Note
This feature is still an experimental. The implementation might change in the future.
You can use kay.generics.crud.CRUDViewGroup in order to define generic CRUD views easily. You just need your own model, modelform definition, and your own templates for rendering htmls.
Let’s see the simplest 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()
def __unicode__(self):
return self.comment
__unicode__ method here is just for rendering entities of this model in a simple way. The default templates for list entities use this method for rendering entities. So, you don’t need this method if you render entities in other way.
myapp/forms.py
from kay.utils.forms.modelform import ModelForm
from myapp.models import MyModel
class MyForm(ModelForm):
class Meta:
model = MyModel
This is a very simple modelform.
Then, you can just create an object for CRUD operation in 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()]
That’s all. You may want to add kay.utils.flash.FlashMiddleware for your convenicence.
settings.py
MIDDLEWARE_CLASSES = (
'kay.utils.flash.FlashMiddleware',
)
Then, you can access ‘/mymodel/list’ for seeing the list of MyModel entities. Here are default mapping rules generated by 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>]])
You can also use string for the values of model and form class attribute for loading modules lazily as follows:
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()]
You can set templates class attribute for using your own templates for rendering html. Here is a simple example:
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'
}
Default templates is set as follows:
templates = {
'list': '_internal/general_list.html',
'show': '_internal/general_show.html',
'update': '_internal/general_update.html',
}
So, for an opener, you can copy kay/_internal/tempaltes/general_***.html to your application’s template directory, and you can edit those files as you like.
Sometimes you need to have some additional values on creating/updating entities other than a modelform takes care about. You can define get_additional_context_on_create or get_additional_context_on_update methods on your own CRUDView classes for this purpose.
These methods must receive request and form instances as arguments, and must return a dictionary. This dictionary will be passed to save() method of your ModelForm instance.
You can use kay.db.OwnerProperty for this purpose. The default value of this property is a current user’s key if user is sienged in, otherwise, None. You need to exclude this property on your form like an example bellow:
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',)
Then, you can just create an object for CRUD operation in urls.py.
You can control which entity to show on the list by defining a get_query instance method on your own CRUDViewGroup subclass.
An example bellow shows how to show entities owned by current user:
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')
As you can see, get_query receives only current request object as its argument, and must return Query instance.
You can limit a particular operation to a particular set of users by defining authorize instance method on your own CRUDViewGroup subclass. These operations are classified in list, show, create, update, delete.
kay.generics package has useful presets for this method, so you can choose one of them if you like.
An example bellow shows how to use one of these presets:
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: detailed docs about authorize method.
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>