Kay-frameworkのテンプレートの読み込み問題

Kayでのテンプレート継承 – Blog@uchikoshi22」より。

「kay/templates/」に同名のファイルがあると「myapp/templates/」よりも優先して読み込まれてしまう問題。

たとえば次のテンプレートでは「myapp/templates/base.html」ではなく「kay/templates/base.html」が読み込まれてしまう。

myapp/templates/index.html

{% extends "base.html" %}
{% block hoge %} EXTENDED? {% endblock %}

この問題はなかなか気がつかないだろう。

回避策だが、「Kayでのテンプレート継承 – Blog@uchikoshi22」にあるようにsettings.pyのTEMPLATE_DIRSで指定する。

TEMPLATE_DIRS = (
  'myapp/templates',
)

あるいは、テンプレートの方で「myapp/base.html」と指定しても良さそうだ。

myapp/templates/index.html

{% extends "myapp/base.html" %}
{% block hoge %} EXTENDED? {% endblock %}

ドキュメントの「7.4. テンプレートの読み込み」では「なお APP_DIR/templates ディレクトリは自動的に読み込みの対象になります。」とあるので、てっきり「myapp/templates/」が優先的に読み込まれると思っていたけれど、そうではないらしい。

最新のKay-fwのソースコードを追いかけていないので、何とも言えないが、自分もはまりそうで気になる。

ぜひともMLに投げていただきたいと思う。

Google App Engine用フレームワークKayでパフォーマンス測定ツール「Appstats」を使用する

Google App Engineに用意されているパフォーマンス測定ツール「Appstats」をKayで使用する方法を紹介します。

app.yamlとsettings.pyを編集し、「Appstats」を有効にします。

app.yaml

handlers:
…
# 追加。普段のハンドラの上に書くこと
- url: /stats.*
  script: $PYTHON_LIB/google/appengine/ext/appstats/ui.py

- url: /.*
  script: kay/main.py

settings.py

MIDDLEWARE_CLASSES = (
  'google.appengine.ext.appstats.recording.AppStatsDjangoMiddleware', #追加
  'kay.auth.middleware.AuthenticationMiddleware', #追加
  …
)

以上の設定で使用できるようになりました。簡単でしたね。

開発環境で動作を確認するには「http://localhost:8080/stats」にアクセスします。

もう少し、使いやすくしましょう。

Administration Console Custom Pagesの機能を使い、Adminコンソールに追加します。

app.yaml

# 追加
admin_console:
  pages:
  - url: /stats
    name: "Stats"

参考にしたページ

Google App Engine Version 1.3.6の新機能

Google App Engine Version 1.3.6の新機能を開発環境で試しました。

サンプルコードはKay-Frameworkを使用しています。

(1)Results of datastore count() queries and offsets for all datastore queries are no longer capped at 1000.

データストアのクエリーとカウントの1000件の制限を解除されました。

データストアのクエリーとカウントの取得件数が1000件までに制限されていました。
この1000件の制限が解除されました。
CPUクオータとリクエスト時間の許す範囲でデータを取得できます。

QueryとGqlQueryの基底クラス(_BaseQuery)で、取得できる件数の初期値が1000件に設定されています。

class _BaseQuery(object):
  def count(self, limit=1000, **kwargs):

このため引数で制限を指定しない場合は、取得できる件数は従来と同じで1000件までです。

count = models.Comment.all().count() #=>最大1000

1000件以上取得したい場合は、引数で指定します。

count = models.Comment.all().count(99999) #=>最大99999

(2) Users can now serve custom static error pages for over_quota, dos_api_denial and default cases.

エラーページの設定が可能になりました。
参照:Custom Error Responses

app.yaml

error_handlers:
  # 標準のエラーページ
  - file: default_error.html

エラーごとに表示するエラーページを設定することもできます。

app.yaml

error_handlers:
  # 標準のエラーページ
  - file: default_error.html

  # リソースの割り当てを超えたとき
  - error_code: over_quota
    file: over_quota.html

  # DoS Protection
  - error_code: dos_api_denial:
    file: dos_api_denial.html

  # タイムアウトのとき
  - error_code: timeout:
    file: timeout.html

(3)Automatic image thumbnailing is now available in the Images API using get_serving_url().

get_serving_url()で画像のサイズ変更や切り抜きができるようになりました。
参照:get_serving_url(blob_key, size=None, crop=False)

Google App Engine 1.3.6 – Ian Lewis」によると、別のインフラを使うためにGoogleAppEngineのクオータの制限がかからないそうです。

以下のコードは、get_serving_url(blob_key, size=None, crop=False)を使用したサンプルコードです。
アップロードされ画像に対してサイズ変更したり、切り抜きをした画像のURLを作成します。

myapp/urls.py

view_groups = [
  ViewGroup(
    Rule('/', endpoint='index', view='myapp.views.index'),
    Rule('/upload', endpoint='upload', view=('myapp.views.UploadHandler', (), {})),
  )
]

myapp/views.py

from werkzeug import Response
from kay.utils import render_to_response, url_for
from kay.handlers import blobstore_handlers
from google.appengine.ext import blobstore
from google.appengine.api import images

def index(request):
    upload_url = blobstore.create_upload_url(url_for('myapp/upload'))
    blob_key = request.values.get('blob_key')
    d = {'upload_url': upload_url}
    if blob_key:
        from google.appengine.api.images import get_serving_url
        d['blob_key'] = blob_key
        # サイズ変更
        d['resize_url'] = get_serving_url(blob_key, 64)
        # 切り抜き
        d['crop_url'] = get_serving_url(blob_key, 160, True)

    return render_to_response("myapp/index.html", d)

class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
    def post(self):
        # 'file' is file upload field in the form                                                                                                              
        upload_files = self.get_uploads('file')
        blob_info = upload_files[0]
        headers = {'Location': url_for('myapp/index', blob_key=blob_info.key())}
        return Response(None, headers=headers, status=302)

myapp/templates/index.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Top Page - myapp</title>
</head>
<body>

{% if blob_key %}
Upload succeeded.<br/>
<a href="{{ resize_url }}">View resizing</a><br />
<a href="{{ crop_url }}">View cropping</a><br />
{% endif %}

<form action="{{ upload_url }}" method="POST" enctype="multipart/form-data">
Upload File: <input type="file" name="file"><br>
<input type="submit" name="submit" value="Submit">
</form>

</body>
</html>

(4)Multitenancy is now supported in the datastore, allowing better compartmentalization of user data.

データストアの名前空間をサポートしました。
参照:Multitenancy and the Namespaces Python API – Google App Engine – Google Code

from google.appengine.api import namespace_manager
namespace = namespace_manager.get_namespace()

namespace_manager.set_namespace('A')  # 名前空間を「A」に設定
do_something() #名前空間「A」でデータストアを操作

namespace_manager.set_namespace('B')  # 名前空間を「B」に設定
do_something() #名前空間「B」でデータストアを操作

namespace_manager.set_namespace(namespace)  # 名前空間を元に戻す

以下のコードは、名前空間を使用したサンプルコードです。
名前空間(‘-global-‘, ‘foo’, ‘bar’)のいずれかのカウンターを1増加し、増加した名前空間とカウンターの値を表示します。

myapp/views.py

from werkzeug import Response
from kay.utils import render_to_response, url_for
from google.appengine.ext import db
from google.appengine.api import namespace_manager
import random

class Counter(db.Model):
    """カウンター"""
    count = db.IntegerProperty()

def update_counter(name):
    """nameのカウンターを1増加する"""
    def _update_counter(name):
        counter = Counter.get_by_key_name(name)
        if counter is None:
            counter = Counter(key_name=name);
            counter.count = 1
        else:
            counter.count = counter.count + 1
        counter.put()
        return counter.count
    return db.run_in_transaction(_update_counter, name)

def index(request):
    """
    名前空間('-global-', 'foo', 'bar')のいずれかのカウンターを1増加し、
    増加した名前空間とカウンターの値を表示する
    """
    # 名前空間のリスト
    ns_list = ('-global-', 'foo', 'bar')
    # 名前空間のリストからランダムに取得
    d = {'ns': random.choice(ns_list)}
    # 現在の名前空間
    namespace = namespace_manager.get_namespace()
    try:
        # 編集するデータストアの名前空間を設定
        namespace_manager.set_namespace(d['ns'])
        # カウンターを増加して結果を取得
        d['count'] = update_counter('SomeRequest')
    finally:
        # 名前空間を元に戻す
        namespace_manager.set_namespace(namespace)
    return render_to_response('myapp/index.html', d)

myapp/templates/index.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Top Page - myapp</title>
</head>
<body>
<p>namespace = {{ns}}</p>
<p>count = {{count}}</p>
</body>
</html>

(5)Added a pause queue button to the task queue details page in the Admin Console.

管理画面の「Task Queues」に「Pause Queue」ボタンが追加されました。

(6)Historical graphs have been added to all of the dashboard graphs in the Admin Console.


このボタンが追加された?

(7)New method to allocate datastore ids in a given range: db.allocate_id_range().

与えられた範囲でデータストアのidを割り当てる新しいメソッド db.allocate_id_range() が追加されました。
参照:db.allocate_id_range()

(8)New db method is_in_transaction() determines if a transaction is still open.

トランザクション中かどうかを取得する新しいメソッド db.is_in_transaction() が追加されました。
参照:db.is_in_transaction()

(9)Increased several rate limited quotas for free applications.

無料枠が増えました。

(10) Remote API now supports the Blobstore API.

リモートAPIが新しくBlobstore APIをサポートしました。

(11) Content-range headers are supported on Blobstore downloads.

英語には自信がありません。(汗
間違いがあれば教えてください。

Kayのログインフォームをカスタマイズする(2) 文言の日本語化する

前回の記事「Kayのログインフォームをカスタマイズする(1) 継承元のテンプレートファイルを変更する « 山本隆の開発日誌」の続き。

Google App Engine用フレームワークKayのデータストアを利用した認証を使用したときのログインフォームをカスタマイズする方法を紹介します。

文言の日本語化する

初期値の状態では、ログインフォームの文言は「user name」や「password」のように英語になっています。

ログインフォームの文言を日本語化するには、Kayのメッセージ国際化の機能を使用します。
※参考:12. メッセージ国際化 — Kay v0.10.0 documentation
※参考:Google App Engine用フレームワーク Kay の国際化の機能を有効にする « 山本隆の開発日誌

settings.py

USE_I18N = True

これだけでログインフォームの文言が日本語化されます。

メッセージを変更したい場合は、メッセージ国際化の機能を使って変更します。

たとえばフォームのタイトル「Kay ログインフォーム」を変更したい場合は次のようにします。

  1. カタログファイルの雛型を作成します。
    python manage.py extract_messages myapp
    

    カタログファイルが myapp/i18n/messages.pot に作成されます。

  2. カタログファイルを編集します。

    myapp/i18n/messages.pot に次の行を追加します。

    msgid "Kay Login Form"
    msgstr ""
    

    変更したい文言のmsgidは myproject/kay/auth/templates/loginform.html を参照して確認します。

  3. 雛型から日本語の翻訳を追加します。

    python manage.py add_translations myapp -l ja
    

    日本語の翻訳ファイルが myapp/i18n/ja/LC_MESSAGES/messages.po に作成されます。

  4. 日本語の翻訳ファイルを編集します。

    msgid "Kay Login Form"
    msgstr "MyApp ログインフォーム"
    

    ファイルの文字コードはUTF-8で保存します。

  5. 翻訳ファイルをコンパイルします。

    python manage.py compile_translations myapp
    

    コンパイルされたファイルが myapp/i18n/ja/LC_MESSAGES/messages.mo に作成されます。

ログインフォームにアクセスすると、文言が変わっていること確認できます。