GAE中使用Google Visualization API

Google Visualization API看上去是个蛮不错的东西,在GAE环境中可以通过移植数据源的方式访问GAE中的数据,以便生成动态的图形。Google visualization python(gviz_api)是可以实现Python中数据类型转换成Visualization API可以使用的json格式的代码,而在django中则可以简单的通过将json数据写入模板来实现。比如:

google.load("visualization", "1", {packages:["table"]});

google.setOnLoadCallback(drawTable);
function drawTable() {
    var json_data = new google.visualization.DataTable({{ json }}, 0.5);
    var json_table = new google.visualization.Table(document.getElementById('table_div_json'));
    json_table.draw(json_data, {allowHtml: true, showRowNumber: true});}		

这个{{ json }}就是通过gviz_api中的ToJSon方法生成的json格式数据。其实就像这个我用数据生成的饼型图利用Google Chart也可以生成图片格式的图形,速度要比Visualization API快得多,这一点让我很难在这个wow-dkp-engine程序中取舍。

后来在使用Visualization API生成这张表格的时候遇到一个数据库的异常:

Timeout: datastore timeout: operation took too long

可能由于数据库运算时间过长,导致GAE服务器得不到数据库的响应。这个问题暂时还不太好解决,可能需要用把这个表格数据都放入缓存,然后让gviz_api去缓存中提取数据。大概也可以通过分页来解决这个问题。暂时先用捕捉异常的方法让程序得以运行,一旦发生异常则停止所有操作,只显示异常发生之前计算好的数据,所以每次访问返还的结果居然还都是不一样的...

使用多选框和HttpRequest提交数据

我记得google.appengine.ext.db的djangoforms里面的确是有个类叫做ModelMultipleChoiceField,但是现在已经没有了,不过ModelChoiceField还在。我目前的问题是想建立由模型生成的表单,通过checkbox提交这个模型中被选择的数据,如果checked那么将所有数据提交到一个模型的ListPropery属性形成一个key的列表,否则的话就不提交。

if request.method == 'POST':
   choosed_players_key = []
   post_players = request.POST.getlist('player')
   choosed_players = models.Players.get(post_players)
   for player in choosed_players:
      choosed_players_key.append(player.key())
      players_list.players = choosed_players_key
   players_list.put()
players_list是一个包含ListProperty的模型的实例,它的这个属性是由db.Key构成的,但是当我直接让:
players_list.players = request.POST.getlist('player')

的时候却发生了错误,players_list.players这个db.Key竟然是Key.from_path()生成的列表,所以后来改成先通过request.POST.getlist('player')获得所有player对象,然后在通过对象获得对象的key,再把这些key形成列表,最后再把这个列表赋值给players_list的players属性,这样竟然就可以了。

参考:附录H HTTP请求(Request)和回应(Response)对象

设计新的BLOG主题

最近一直在设计这个新的TextStyle主题,总想用最简约的外观和KISS原则把这个Blog程序的前台和后台都重新设计一下,这除了是受到国内一些知名博客所使用风格影响可能也是长期使用Google出品的程序所致。此外还仿照http://www.blogger.com的Favicon设计了一个新的Favicon。在这个新的TextStyle主题里,我几乎去掉了所有js代码和图片,少部分必须保留的图片也准备在以后自己动手重新设计。布局方面依然沿用两栏式的Blog典型布局,顶部的Header移动到侧边栏的左上角,标题准备使用图片制作一个类似lxml的这个可爱的大蛇丸Logo。

Keep it simple, stupid!

参考:

Python中的__str__方法

Python中类的__str__方法可以用来设置当使用某个对象的str()方法时需要返还的字符,在django里有些表单在使用关系模型的时候会用到这个方法,比如ModelChoiceField会调用__str__的返还值作为选项。有时候会因为django和Python使用字符编码不同造成错误:

UnicodeEncodeError: 'ascii' codec can't encode characters

使用Python中的另一个方法__unicode__可以解决这个编码的问题。

django.forms中ChoiceField的一个问题

按照django源代码中ChoiceField的构造函数只需要定义Choices这个参数就可以构建一个选择按钮。

class ChoiceField(Field):
    def __init__(self, choices=(), required=True, widget=Select, label=None, initial=None, help_text=None):
        super(ChoiceField, self).__init__(required, widget, label, initial, help_text)
        self.choices = choices

这个Choices参数可以是一个复合的列表或者元组类型,一个表示Choice的名称另一个表示Choice的值。通过这样构造表单应该可以按照选定的Choice来返还值。比如我需要通过一个名为Catagroy的models建立这个Choices,Catagory包括两个字段name和slug,那么这段代码应该类似:

choices=[(catagory.name,catagory.slug) for catagory in Catagory.all()]

这段代码在appengine里似乎有些问题,choices总是不能包括最近添加的选项。也就是说,本来你已经更新了添加了新的catagory,但是ChoiceField的选项并未随之更新,而ChoiceField的更新只在重启Develop Server或者改写代码之后,程序上传到服务器也是一样。不知道这是不是一个bug,或者Django的ChoiceField不是这样的用法。

最后我选择了用ModelChoiceField来代替ChoiceField,和ChoiceField不同的是ModelChoiceField接受的是一个查询,返还的是一个key而不是一个值,所以和Catagory关联的Post的catagory字段需要定义成一个ReferenceProperty。

catagory = djangoforms.ModelChoiceField(Catagory,query=Catagory.all())

App Engine项目app-engine-patch

app-engine-patch是一个类似appengine-helper的开源项目,主要作用还是在App Engine上更好的实现django的一些功能,与appengine-helper不同的是app-engine-patch支持的东西更多一些,按照他们的说法app-engine-patch最低限度支持django1.0。

主要特性:

  • 支持manage.py
  • 支持Django的authentication framework(不需要google帐户)
  • 支持Django的后台数据库和线程
  • 支持Memcache
  • 支持Django的电子邮件功能
  • 支持django.forms包中的大部分ModelForms
  • 支持Django的testing framework
  • 取代Python原有的httplib(支持boto)
  • 集成profiler
  • 支持通用视图

ragendja(代码库):

  • 增强管理框架:你可以使用你自己的用户类
  • LoginRequiredMiddlewar中间件能够使你不再需在视图前添加@login_required
  • 基于RequestContext的render_to_response()(这个好像本身app engine就支持)
  • 改进的模板载入功能去掉了文件名称空间的前缀
  • 全局的模板tag
  • @transaction decorator
  • JSONResponse
  • ModelTestCase可以改变DB内容
  • 更多

在JIEblog上实现XML-RPC

虽然写这篇文章的时候我的这个API还有好多问题,但我还是迫不及待的想把实现这个功能的过程记录一下。最初的动力就是想能够使用WLW(Windows Live Writer)来发布文章,实际上也就是实现一个简单的XML-RPC。所谓XML-RPC就是:remote procedure call,即远程程序调用。它采用HTTP 作为传输协议,并使用XML词汇表作为消息的载体。

这里要实现的是一个XML-RPC的服务端,Python中的SimpleXMLRPCServer,就是用来实现XML-RPC服务端的模块。首先要导入SimpleXMLRPCServer的SimpleXMLRPCDispatcher模块和django的HttpResponse:

from SimpleXMLRPCServer import SimpleXMLRPCDispatcher
from django.http import HttpResponse

然后使用POST方法写入:

response = HttpResponse()
if len(request.POST):
response.write(
dispatcher._marshaled_dispatch(request.raw_post_data)
)

最后通过把函数注册给dispather:

dispatcher.register_function(
server_function_name,'client_function_name'
)

WLW支持的API的确是非常全,包括很多知名和未知名的Blog API,比如BloggerWordpressdasBlogTypepadMovable Type等,最终还是选择了Metaweblog API…这个能参考的代码比较多。

Metaweblog API主要的三个函数是:

  • metaWeblog.newPost (blogid, username, password, struct, publish)用来发布新文章并返还字符串
  • metaWeblog.editPost (postid, username, password, struct, publish)用来编辑文章并返还一个布尔值
  • metaWeblog.getPost (postid, username, password) 用来获得一篇文章并返还struct

此外还需要使用Blogger API中的一些函数来完善:

  • blogger.getUsersBlogs(appkey,username, password)返还程序的根目录、ID和名字
  • blogger.deletePost(appkey,username, password)用来删除一篇文章

Metaweblog API中还定义了一些方法:

  • metaWeblog.newMediaObject(blogid, username, password, struct)返还struct
  • metaWeblog.getCategories (blogid, username, password)返还structs
  • metaWeblog.getRecentPosts (blogid, username, password, numberOfPosts)返还struct的数组

在看了这篇文章后发现原来blogger已经实现了新的Blogger Data API,以后可能会考虑在JIEblog上实现这个新的api,毕竟这种方法应该可以直接进行用户的验证。

何为中间件,自定义错误页面遇到的问题

在JIEblog里面请求一个不存在的url返还的并不是django定义的错误页面,而是一个appengine的google服务器错误页面:

Error: Server Error

The server encountered an error and could not complete your request. If the problem persists, please report your problem and mention this error message and the query that caused it.

查看代码以后发现是因为在生成setting.py文件的时候留了一行中间件的定义:

MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
)

所谓中间件the django book这样解释:

中间件组件是遵循特定API规则的简单Python类

而这个django.middleware.common.CommonMiddleware的主要作用是:

  • 禁止用户访问没有在url列表中列出的url地址
  • 标准化url请求,在地址后面自动添加"/"
  • 依据USE_ETAGS的设置处理Etag

这个中间件会在用户连续请求不存在的url时,暂时禁止用户访问网站。不过这个错误页面好像没有错误代码,因而不能自定义这个页面。最后我还是决定保留这个setting,因为这好像是GAE中唯一能用的CommonMiddleware。

参考:

在GAE中实现的文件上传和下载

blzinsider uploader

作为一个djangoGAE的初学者,直接使用GAE支持的那部分django编写程序并不是一个很好的选择,而这个Blog就是用这种方法写的,不过既然开始就错了,还是让这个错误继续下去吧...

使用这种方法在很多情况下都可能遇到django手册中无法实现的东西,在编写这段程序的时候首先就发现djangoforms没法使用FileField(),不过手册里面也有其他的方法:

if request.method == 'POST':
for f,file_info in request.FILES.items():
file=models.FileUpload(filename=file_info['filename'],
content_type=file_info['content-type'],
content=db.Blob(file_info['content']),
owner=users.get_current_user(),
size=len(db.Blob(file_info['content'])))
file.put()
return HttpResponseRedirect('/uploader')

首先构建一个名为FileUpload并且包含上面属性的model,Blob用来存放二进制的文件。上面这段代码是的django的一个HttpRequest object,request.FILES是一个包括三个属性的字典类型:content,filename,content-type。这样文件就会存放在数据库的content属性里。

接下来是要在网页上输出这个文件,首先注意的是上传的文件并没有存放在网站上而是存放在FileUpload这个数据库的content里,所以输出这个文件只要通过每个字段的key就可以了。

the_file=models.FileUpload.get(the_key)
response=HttpResponse(the_file.content)
response['Content-Type']=the_file.content_type
return response

这段代码是生成一个django的HttpResponse objects,指定Content-Type是为了让程序能够识别文件类型,也可一写成:

response=HttpResponse(
the_file.content,mimetype=the_file.content_type)

或者直接指定文件的类型。如果加入Content-Disposition会使程序直接下载该文件:

response['Content-Disposition']=
'attachment;filename=%s'%the_file.filename

参考:

Creative Commons 3.0 BY