ノート
この機能はまだ実験段階です。将来仕様が変わる可能性があります。
kay.generics.crud.CRUDViewGroup を使用すると、汎用的な CRUD の view を簡単に定義できます。CRUDViewGroup を使用するには、モデル・モデル フォーム・テンプレートさえあれば良いです。
一番単純な例を見てみましょう
myapp/models.py
# -*- coding: utf-8 -*-
# myapp.models
from google.appengine.ext import db
# Create your models here.
class MyModel(db.Model):
comment = db.StringProperty()
def __unicode__(self):
return self.comment
ここで定義している __unicode__ メソッドは簡単にこのモデルのエンティ ティを表示するためのものです。デフォルトのテンプレートが表示のためにこ のメソッドを使用しますので、独自テンプレートを使用して、他の方法で表示 するならば必要ありません。
myapp/forms.py
from kay.utils.forms.modelform import ModelForm
from myapp.models import MyModel
class MyForm(ModelForm):
class Meta:
model = MyModel
単純なモデルフォームです。
最低限これだけあれば、CRUD用のオブジェクトを作れます。ここでは urls.py に書きましょう。
myapp/urls.py
# -*- coding: utf-8 -*-
# myapp.urls
from kay.generics import crud
from myapp.forms import MyForm
from myapp.models import MyModel
class MyCRUDViewGroup(crud.CRUDViewGroup):
model = MyModel
form = MyForm
view_groups = [MyCRUDViewGroup()]
これだけです。ただ下記のように kay.utils.flash.FlashMiddleware を追 加した方が便利なので追加しましょう。
settings.py
MIDDLEWARE_CLASSES = (
'kay.utils.flash.FlashMiddleware',
)
こうすると ‘/mymodel/list’ にアクセスすれば MyModel のエンティティ 一覧を表示できます。下記は MyCRUDViewGroup で作られるデフォルトのマッピ ングルールです。
Map([[<Rule '/mymodel/list' -> myapp/list_mymodel>,
<Rule '/mymodel/list/<cursor>' -> myapp/list_mymodel>,
<Rule '/mymodel/show/<key>' -> myapp/show_mymodel>,
<Rule '/mymodel/create' -> myapp/create_mymodel>,
<Rule '/mymodel/update/<key>' -> myapp/update_mymodel>,
<Rule '/mymodel/delete/<key>' -> myapp/delete_mymodel>]])
model と form class attribute には文字列も使用できます。文字列 で指定するとモジュールを遅延ロードできます。
myapp/urls.py
# -*- coding: utf-8 -*-
# myapp.urls
from kay.generics import crud
class MyCRUDViewGroup(crud.CRUDViewGroup):
model = 'myapp.models.MyModel'
form = 'myapp.forms.MyForm'
view_groups = [MyCRUDViewGroup()]
templates class attribute を指定すれば、独自のテンプレートが使用で きます。下記に例を示します:
class MyCRUDViewGroup(crud.CRUDViewGroup):
model = 'myapp.models.MyModel'
form = 'myapp.forms.MyForm'
templates = {
'show': 'myapp/mymodel_show.html',
'list': 'myapp/mymodel_list.html',
'update': 'myapp/mymodel_update.html'
}
デフォルトのテンプレートは下記のようになっています:
templates = {
'list': '_internal/general_list.html',
'show': '_internal/general_show.html',
'update': '_internal/general_update.html',
}
まずは手始めとして、 kay/_internal/tempaltes/general_***.html をア プリケーションのテンプレートディレクトリにコピーして、それらを編集する のが楽でしょう。
時には、エンティティの作成・更新時にモデルフォームで定義する以外の値を 渡したい事もあります。そのためには CRUDViewGroup のサブクラスに get_additional_context_on_create や get_additional_context_on_update インスタンスメソッドを定義します。
これらのメソッドは request と form インスタンスを引数として受け 取り、dict を返します。この dict は ModelForm の save() メソッドに 渡されます。
kay.db.OwnerProperty を使用すると簡単に、現在のユーザーを保存できま す。このプロパティのデフォルト値はユーザーがログインしていればそのユー ザーの key で、そうでなければ None です。下記の例のように ModelForm ではこのプロパティは除外する必要があります:
myapp/models.py
# -*- coding: utf-8 -*-
# myapp.models
from google.appengine.ext import db
from kay.db import OwnerProperty
# Create your models here.
class MyModel(db.Model):
user = OwnerProperty()
comment = db.StringProperty()
def __unicode__(self):
return self.comment
myapp/forms.py
from kay.utils.forms.modelform import ModelForm
from myapp.models import MyModel
class MyForm(ModelForm):
class Meta:
model = MyModel
exclude = ('user',)
urls.py は変更しなくとも大丈夫です。
CRUDViewGroup サブクラスの get_query メソッドを定義する事で、ど のエンティティを一覧に表示するかコントロールできます。
下記の例では、現在ログイン中のユーザーが所有するエンティティのみ表示す る事ができます。
class MyCRUDViewGroup(crud.CRUDViewGroup):
model = 'myapp.models.MyModel'
form = 'myapp.forms.MyForm'
def get_query(self, request):
return self.model.all().filter('user =', request.user.key()).\
order('-created')
見て分かるとおり、 get_query は現在の request を引数として取り、 Query インスタンスを返します。
特定の操作を特定のユーザー・グループに制限するには、 CRUDViewGroup サブクラスに authorize インスタンスメソッドを作成します。操作は list, show, create, update, delete に分類されていま す。
kay.generics パッケージには便利なプリセットの関数がいくつか用意され ていて、これらの中から選んで使う事もできます。
下記の例ではこのうちの一つを使用しています:
from kay.generics import only_owner_can_write_except_for_admin
from kay.generics import crud
class MyCRUDViewGroup(crud.CRUDViewGroup):
model = 'myapp.models.MyModel'
form = 'myapp.forms.MyForm'
authorize = only_owner_can_write_except_for_admin
TODO: authorize メソッドに関する詳細な説明
You can use kay.generics.rest.RESTViewGroup in order to create RESTfull APIs easily. You can create various handlers for RESTfull services of specified models.
Let’s see a simple example.
myapp/models.py:
# -*- coding: utf-8 -*-
# myapp.models
from google.appengine.ext import db
# Create your models here.
class MyModel(db.Model):
comment = db.StringProperty()
created = db.DateTimeProperty(auto_now_add=True)
Its a simple model for just storing comments. You can create RESTfull view groups as follows:
myapp/urls.py:
# -*- coding: utf-8 -*-
# myapp.urls
#
from kay.routing import (
ViewGroup, Rule
)
from kay.generics.rest import RESTViewGroup
class MyRESTViewGroup(RESTViewGroup):
models = ['myapp.models.MyModel']
view_groups = [
MyRESTViewGroup(),
ViewGroup(
Rule('/', endpoint='index', view='myapp.views.index'),
)
]
This will give you following Method/URL combinations for RESTfull access to this model, assuming that myapp is mounted at ‘/’. All the <typeName> in the example bellow is ‘MyModel’ in this case.
GET http://yourdomain.example.com/rest/metadata
GET http://yourdomain.example.com/rest/metadata/<typeName>
GET http://yourdomain.example.com/rest/<typeName>
GET http://yourdomain.example.com/rest/<typeName>?offset=50
GET http://yourdomain.example.com/rest/<typeName>?<queryTerm>[&<queryTerm>]
Gets a page of <typeName> instances using a query filter created from the given query terms (with offset features mentioned above). Multiple query terms will be AND’ed together to create the filter. A query filter term has the structure: f<op>_<propertyName>=<value>
Examples:
Available operations:
Blob and Text properties may not be used in a query filter
GET http://yourdomain.example.com/rest/<typeName>/<key>
POST http://yourdomain.example.com/rest/<typeName>
POST http://yourdomain.example.com/rest/<typeName>/<key>
PUT http://<service>/rest/<typeName>/<key>
DELETE http://<service>/rest/<typeName>/<key>
By default, you need to create XML elements as the payload for POST and PUT requests, but you can also use json payload by setting “Content-Type” request header to “application/json”.
By default, the result set is served in XML format, but you can also get json response by setting “Accept” request header to “application/json” as well.
Here is an example for guestbook implementation with using jquery’s ajax request.
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>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
<script type="text/javascript">
function deleteEntity(key) {
$.ajax({
type: "DELETE",
url: "/rest/MyModel/"+key,
success: function(data) {
refreshData();
}
});
}
function displayEntity(entity) {
$("#comments").append(entity.comment+
"<i> at " + entity.created + "</i>"+
' <a href="#" onclick="deleteEntity(\''+entity.key+'\');">x</a><br>');
}
function refreshData() {
$.ajax({
type: "GET",
url: "/rest/MyModel?ordering=-created",
dataType: "json",
success: function(data) {
$("#comments").html("");
if (data.list.MyModel) {
if (data.list.MyModel.key) {
displayEntity(data.list.MyModel);
} else {
for (var i=0; i < data.list.MyModel.length; i++) {
displayEntity(data.list.MyModel[i]);
}
}
}
}
});
$("#comment").focus();
}
function sendData() {
$("#sendButton").attr("disabled", "disabled");
$.ajax({
type: "POST",
url: "/rest/MyModel?type=full",
dataType: "json",
contentType: "application/json",
data: JSON.stringify({"MyModel": {"comment": $("#comment").val()}}),
success: function(data) {
$("#comment").val("");
$("#sendButton").attr("disabled", "");
refreshData();
}
});
}
$(document).ready(function(){
$("#comment").keypress(function(e) {
if (e.which == 13) {
sendData();
}
});
refreshData();
});
</script>
</head>
<body>
<input type="text" id="comment">
<input type="button" onclick="sendData();" value="send" id="sendButton">
<div id="comments"></div>
</body>
</html>