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())

为JIEblog生成符合规范的网站供稿

最近着手改进网站的代码,一方面通过memcache降低CPU使用,另一方面想把网站供稿在feedburner上烧制一下,以减少网络程序访问产生的CPU占用。以前的供稿在Google Reader上虽然显示正常,但在Feedburner上烧录后却只有乱码。研究了一下发现是由于feedburner是根据供稿的编码来识别代码,而不是根据实际的编码,因为这个XML模板是用Intype默认的ANIS编码编写的所以造成了这个错误。

另外用http://feedvalidator.org/验证了一下这个供稿,发现还有不少问题。文章内部字符和标记的错误就不计了,剩下的主要是时间格式不符合规范。其实以前这个时间规范的问题一直会影响Google Reader,不过不知道Google什么时候改正了时间显示的方式,用Google Reader抓取的时间代替了供稿生成的时间,所以我的这个问题也一直没有修正。

Atom使用的这个时间格式标准被称为rfc3339 Timestamps,其实编写一个能生成符合这个rfc3339标准的python代码也不算难,而我可能以后会用django生成的供稿,所以为了节约时间还是直接Google了一个rfc3339 for Python,上面还有Pyfeed这种东西,真是遗憾了,以后干什么一定要先Google一下。

在用http://jieblog.appspot.com烧录的时候发现feedburner居然直接识别了appspot的子域名并且返还了这么个地址http://feeds.feedburner.com/appspot/euFN。不过在没有修改完善之前还是不使用这个供稿了,毕竟修改供稿造成阅读器的项目混乱是很麻烦的。

为JIEblog添加Memcache

虽然从控制台上看Application Quotas还都够用,但是Current Load里首页和feeds两个项目平均CPU占用都有警告:

This URL uses a heigh amount of CPU and may soon exceed its quota.

看着总是不太舒服。首页的高CPU自然比较正常,而feeds因为我没有使用feedburner这类的工具烧制RSS现在的确有点后悔了,再加上这个feeds还是Google Webmaster工具的Sitemap所以占用也会很高。而Google Reader甚至有时候在我发布文章后1分钟内就会更新。

Appengine的FAQ里提到衡量CPU使用的两个主要途径是:

  1. Runtime CPU:应用程序本身产生的CPU使用,这显然是和代码的质量有关系
  2. API CPU:用于调用App Engine API产生的CPU使用,例如:datastore API , urlfetch API, image API, 等。

第一条我显然没有太多时间做。。。所以直接转向第二条,使用Memcache进行优化。Appengine文档里有个很简单的例子,目前我只用Memcache尝试提取首页的Post数据,然后看看效果是否明显:

def get_data(the_key, time_exp):
  data = memcache.get("Post")
  if data is not None:
	return data
  else:
	data = query_for_data()
	if not memcache.add(the_key, data, time_exp):
		logging.error("Memcache set failed.")
	return data

通过使用这个函数,首先检查memcache的data是否有the_key这个数据,如果有的话直接返还data,如果没有的话则调用query_for_data()这个函数。然后将query_for_data()的结果加入memcache,而memcahe则是按照设定的time_exp进行更新。

gist.github的一些插件

logo_gist

gist.github是一个类似pastie的代码存储服务。最近Github的博客上介绍了几个在vim、emaces和textmate中使用gist.github的插件。emacs我不怎么用,而textmate我也没有Mac,单介绍一下vim的这个插件Gist.vim。

首先可以从vim的网站或者gihub repo下载这个vim插件,然后复制到vim的plugin目录里面。启动vim,如果你之前没有设定git的环境变量的话需要首先通过下面命令设置:

git config --global github.user yourname
git config --global github.token your API Token

你可以在https://github.com/account这个页面的Global Git Config连接找到你的设置命令。

gist.vim提供了一些命令可以在vim中提交或者查询你在gist中的文件:

:Gist
post whole text to gist.
:'<,'>Gist
post selected text to gist.
:Gist -p
post whole text to gist with private.
:Gist XXXXX
edit gist XXXXX.
:Gist -l
list gists from mine.
:Gist -la
list gists from all. 

当然如果你在安装了gist.vim运行vim的以后vim提示你缺少git或者curl命令的时候你需要首先安装这两个命令。

sudo apt-get install git
sudo apt-get install curl

配置YUI的Rich Text Editor不调用cleanHTML方法

从tinyMCE转到YUI的Rich Text Editor以后好长时间我才发现YUI的这个范例配置的Code Editor会自动调用cleanHTML方法格式化代码,于是好多标签比如script这种标签会直接删除掉,这直接导致我以前配置的swf-mp3-player和prettyprint这两端代码不能使用。

要使YUI的Editor不调用cleanHTML首先需要在配置中把handleSubmit的属性改成false,这样在submit的时候Editor就不会调用cleanHTML了;但点击Code View这个button的时候还是会调用this.cleanHTML()这个方法,直接去掉的话Code View这个button基本上就没用保存文字的功能了,于是查了一下YUI的API,果然有this.getEditorHTML()这个方法,用this.cleanHTML()取代this.cleanHTML()就好了。

添加Gravatar支持

Gravatar是一个支持在网络上引用图片的服务器,用户把头像图片上传到Gravatar服务器上,然后支持Gravatar的网站就会根据用户的email找到用户在Gravatar上传的图片。至少我看到wordpress.com和wordpress通过插件支持这个服务,另外最近看到Github也支持。

添加Gravatar只要通过python生成用户图片的地址就可以了:
# 导入用到的lib
import urllib, hashlib

# 设置变量
email = "someone@somewhere.com"
default = "http://www.somewhere.com/homsar.jpg"
size = 40

# 构建url
gravatar_url = "http://www.gravatar.com/avatar.php?"
gravatar_url += urllib.urlencode({'gravatar_id':hashlib.md5(email).hexdigest(),'default':default,'size':str(size)})

gravatar还支持给图片分级,一般网站允许G-rated就好了,貌似允许X-rated级的会有些危险。

JIEblog source code host on Github.com

JIEblog Source Code
以前一直是用Google Code+Subversion管理代码,现在迁移到ubuntu以后决定尝试一下使用Git,服务器端使用了github.com,唯一不太方便的地方就是github.com在国内不能正常访问。
我最早接触到使用Git的项目既不是linux kernel也不是Rails等什么的开源项目,而是从forum.wowace.com上看到的一些WOW插件作者在使用git管理插件代码,这里面最著名的是oUF等插件的作者Haste的git,还有tekkub等人。我想他们选择git大概是因为开发插件这种东西需要不断的快速的提交,git自然是在适合不过了。

开始编写JIEblog后台

如果只是自己用这个Blog就像以前一样有个富文本编辑器就足够了,但是我准备把这个Blog做成一个简易的个人Blog系统,这势必就需要编写一个能够让用户轻松使用的后台,以后可能还会添加一些导入和导出数据的功能。

自从添加了TinyMCE以后JIEblog的文件已经达到12m,于是决定用YUI Rich Text Editor来替换这个TinyMCE。不过YUI的文件也不小,最后按照Rich Text Editor需要的文件精简下来也有1m左右。另外在submit表单的时候遇到个问题,textarea总是显示没有字符因而不能通过django的表单验证。开始我以为是beform.py这个文件哪里出了问题,最后终于在邮件列表上搜到YUI需要在配置脚本里加入这个属性handleSubmit: true。

Windows Live Writer的Customization API

WLW的Customization API实际上是通过读取站点上一个xml文件——wlwmanifest.xml来实现的,WLW在设置的时候按照这个文件设置API兼容性、侧边栏链接按钮、自定义布局和预览。

weblayout.htm和webpreview.htm分别用来设置Edit和Preview,需要在里面加入{post-title}和{post-body}来进行表示文章的标题和内容。

按照Blog的各种属性设置好wlwmanifest.xml,然后在head中加入:<link href="url wlwmanifest.xml" type="application/wlwmanifest+xml" rel="wlwmanifest">

这样WLW就能自动使用Blog主题了。

在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,毕竟这种方法应该可以直接进行用户的验证。

GAE在线网址搜藏项目——meja

http://meja.blzinsider.cn
http://meja.appspot.com
启动一个在线网址搜藏项目,依然是GAE+django,因为这个应用可能用到javascript的地方比较多,所以决定加入yui。(meja是以前一位朋友所钟爱的瑞典女歌手的名字,碰巧在appspot还能用)

meja需要的一些功能:

  • 多用户支持
  • 从firefox收藏夹导入导出
  • 输出atom和json
  • ajax编辑界面
  • 简单后台

在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

参考:

在GAE中使用Favicon

首先需要把favicon上传到一个静态目录里:

- url: /images
static_dir: images

然后在head标签中加入:

<link href="/images/favicon.ico" rel="shortcut icon" type="image/x-icon">

在app.yaml中指定favicon的位置:

- url: /favicon.ico
static_files: images/favicon.ico
upload: images/favicon.ico
mime_type: image/x-icon

一个在线制作favicon的工具,一般来说合成后的ico文件最好是包含16x16和32x32两种像素的,16x16可以显示在浏览器的地址栏而32x32则可以显示为桌面快捷方式。

利用google提取favicon,像这样:
http://www.google.com/s2/favicons?domain=jieblog.appspot.com

加入google的代码高亮工具prettify

class Post(db.Model):
title = db.StringProperty(required=True)
content = db.TextProperty()
author = db.UserProperty()
post_on = db.DateTimeProperty(auto_now_add=True)
tags = db.StringListProperty()
comments_count = db.IntegerProperty(0)

def __str__ (self):
return '%s' %self.title

def get_absolute_url (self):
return '%s' %self.key().id()

def get_comments_count(self):
return Comments.all().filter("post",self).count()

prettify应该是google在code.google.com上使用的一个代码高亮工具,非常小巧配置也很简单。支持类似C、Bash、和xml的语言,不需要指定语言类型就能够自动识别语法。a44fda46

Creative Commons 3.0 BY