GAE开发问题总结及心得一览
从接触GAE(Google App Engine)就可以想着在上面开发一个自己的程序进行试验,恰好有两个想法,一个是做一个公司部门使用的工作日志系统,可以由领导阅览每个人的工作日志;一个是想做一个个人记账网站,这主要是从我自己的需求出发的,父母老教育自己要有理财的意识,可惜就是没有这个意识,自己到底有多少钱花了多少钱都从来没有想过去整理和记录。时间有限,就选择了自己比较接近的记账网。
由于平时工作都只是做一些后台的设计工作,并没有太多做web的实际经验,制作过程中还是经历了很多痛苦的阶段,不过总算在摸索中磕磕绊绊完成了,下面就把制作过程遇见的问题进行一下汇总,和大家分享,希望大家遇见类似问题时有所帮助。
界面
Web程序最重要的是界面设计了,要有专门的美工才算专业。自己的小程序就没有想要那么专业的美工了,看着不太难看就行了。制作中还是长进了不少,重新学习了CSS。因为这个小程序内容不会占太长的篇幅,就把整体的框架设计成有阴影的box,上面是标题,左边命令栏,右边内容的框架。然后按照自己的想法,在Photoshop里画了出来,又请教了做美工的一个朋友,大体学了一下切图,就基于table+css把界面画出来了。把能做成重复的图片用css设置背景,减少下载图片的网络流量。
框架的选择
GAE本身提供web框架,但考虑到对django比较熟悉打算使用django,但经过测试怎么也不成功,就放弃了。对GAE提供的Web框架进行了学习,感觉和django的很类似,没有什么门槛。不过YAML的设置还是要注意的,特别是static,找了半天才搞明白。
数据库使用的问题
GAE提供BigTable的数据库,面向对象的数据库,不用再考虑ORM的问题了,使用起来还是蛮方便的。但使用过程中还是出现了一些误解,值得总结一下。
1:多个对象的References。如果一个Model的属性中要存放多个对象References怎么实现呢?如一个“支出项”有多个Tag。其实这可以看做是References的List。如下:
class VTag(db.Model): user = db.ReferenceProperty(VUser,required=True) name = db.StringProperty(required=True)class VInOut(db.Model): user = db.ReferenceProperty(VUser) tags = db.ListProperty(db.Key, "Tags")这样当然就没有自动调入的功能了,如果要访问,可以通过下面的方法:
sql = db.GqlQuery("SELECT * FROM VInOut")inouts = sql.fetch(1000)for inout in inouts: rawtags = db.get(inout.tags) rawtags就是由inout.tags为Key的VTag的一个对象列表了。
2:日期型属性的Bug。使用过程中发现了一个问题,就是如果Model的属性是日期型Date,则在查询或过滤条件中出现Bug,解决的方法是将Date型改为Datetime型。具体的可参加《 GAE Gqlquery Date属性不能设置为过滤条件的Bug》。
3:中文的问题。缺省情况下,数据库编码是ASCII编码,存入中文(UTF8编码)时会出现Bug,尤其是对于不太注意编码的朋友,可以参考《 Python中使用中文》。在编写程序时最好将所有的文件(程序文件、静态HTML模板等)都用统一编码方式,推荐用UTF-8。
还有,当从HTML的Form中接收到字符串数据的时候,一定要将送来的数据显式编码为UTF-8,如self.request.get('memo').encode('utf-8'),否则也会出问题。
另外,为了解决中文写入数据库时出错的问题,可以在写入数据库前做如下操作:
<ol class="dp-cpp"><li class="alt"><span><span>import sys reload(sys) sys.setdefaultencoding(</span><span class="string">'utf8'</span><span>) </span></span></li></ol>
或者
<ol class="dp-py"> <li class="alt"><span><span>code = sys.getdefaultencoding() </span></span></li> <li class=""> <span></span><span class="keyword"><strong><font color="#006699">if</font></strong></span><span> code != </span><span class="string"><font color="#0000ff">'utf8'</font></span><span>: </span> </li> <li class="alt"><span> reload(sys) </span></li> <li class=""> <span> sys.setdefaultencoding(</span><span class="string"><font color="#0000ff">'utf8'</font></span><span>) </span> </li> </ol>
其他的方法都试了,不太好使,只有这个非常管用!还有个奇怪的事情,GAE的开发环境不支持重复reload,会不能渲染网页,也就是说第一种方法会不能正常工作。所以最好用第二种方法,这样的话第一次刷新会出问题,后面刷新就不会有问题了。GAE的运行环境这两种是相同的,但是较长时间没有登陆网站的话偶尔还是会出现刷新白屏的Bug,这确实是由于重新载入sys造成的,所以首页最好不要reload sys,需要存入数据库的时候才重新载入sys并设置UTF-8为缺省编码。
4:Index.yaml的问题。index.yaml会由系统自动生成和更新,但是如果是没有这个文件就把系统直接上传到GAE,GAE会花十几个小时才能完全更新。在这段时间如果用到了,会出现need index的错误提示。解决的方法是,在本地完全测试,把生成的index.yaml直接上传,就不会出现上面的问题了。
5:密码md5保存问题。密码保存采用了md5哈希算法,按道理md5出来后是string,可以用StringProperty保存,但发现存是可以存,取出来再和正确的重新md5计算结果比较,会不同。解决的方法就是不用StringProperty而用BlobProperty,取出来后强制str就可以了。
6:TextArea多行出错的问题。这个问题是不小心造成的。当用StringProperty属性存储HTML的文本区(TextArea)内的字符串时,如果在TextArea中回车换行,存入数据库时会出现BadValueError: Property memo is not multi-line的错误。解决很简单,用TextProperty代替StringProperty,同时还要注意存入时也要明确编码为UTF-8,否则会报错,如:
<ol class="dp-py"><li class="alt"><span><span>outthing.memo = db.Text(memo, </span><span class="string"><font color="#0000ff">'utf-8'</font></span><span>) </span></span></li></ol>
session的问题
Web程序很大一部分会用到Sessions的,原先使用PHP、django时根本不用考虑Sessions是如何实现的,只要用就可以了。到了GAE,竟然没有内置的Sessions支持。好不容易找到了utilities,做了简单的测试可以使用就没管其他的了。可是真正使用的时候发现,Sessions中竟然只能存放string对象。不至于把所有的对象都变为string,取回来再变成其他对象吧。呵呵,都在进步,最新的utilities已经支持存放其他类型的对象了。有兴趣的可以看看它的代码,用的pickle。
最新的utilities Sessions(V0.5.1)实现中还是有一定的Bug。实现中使用了GAE的memcache,Session类有个memcache成员用于存储缓存的对象,但是由于会不定期的清除,造成访问某些对象时出错,主要的是__getitem__函数,我写到下面大家可以看看区别。
原始的:
<ol class="dp-py"> <li class="alt"><span><span class="keyword"><strong><font color="#006699">def</font></strong></span><span> __getitem__(</span><span class="special">self</span><span>, k): </span></span></li> <li class=""> <span> </span><span class="comment"><font color="#008200">""" </font></span> </li> <li class="alt"><span><span class="comment"><font color="#008200"> __getitem__ is necessary for this object to emulate a container. </font></span> </span></li> <li class=""><span><span class="comment"><font color="#008200"> """</font></span><span> </span></span></li> <li class="alt"> <span> </span><span class="keyword"><strong><font color="#006699">if</font></strong></span><span> k </span><span class="keyword"><strong><font color="#006699">in</font></strong></span><span> </span><span class="special">self</span><span>.cache: </span> </li> <li class=""> <span> </span><span class="keyword"><strong><font color="#006699">return</font></strong></span><span> pickle.loads(str(</span><span class="special">self</span><span>.cache[k])) </span> </li> <li class="alt"> <span> </span><span class="keyword"><strong><font color="#006699">if</font></strong></span><span> k </span><span class="keyword"><strong><font color="#006699">in</font></strong></span><span> </span><span class="special">self</span><span>.memcache: </span> </li> <li class=""> <span> </span><span class="keyword"><strong><font color="#006699">return</font></strong></span><span> </span><span class="special">self</span><span>.memcache[k] </span> </li> <li class="alt"> <span> data = </span><span class="special">self</span><span>.get(k) </span> </li> <li class=""><span> ...... </span></li> <li class="alt"><span> </span></li> </ol>
我修改后:
<ol class="dp-py"> <li class="alt"><span><span class="keyword"><strong><font color="#006699">def</font></strong></span><span> __getitem__(</span><span class="special">self</span><span>, k): </span></span></li> <li class=""> <span> </span><span class="comment"><font color="#008200">""" </font></span> </li> <li class="alt"><span><span class="comment"><font color="#008200"> __getitem__ is necessary for this object to emulate a container. </font></span> </span></li> <li class=""><span><span class="comment"><font color="#008200"> """</font></span><span> </span></span></li> <li class="alt"> <span> </span><span class="keyword"><strong><font color="#006699">if</font></strong></span><span> k </span><span class="keyword"><strong><font color="#006699">in</font></strong></span><span> </span><span class="special">self</span><span>.cache: </span> </li> <li class=""> <span> </span><span class="keyword"><strong><font color="#006699">return</font></strong></span><span> pickle.loads(str(</span><span class="special">self</span><span>.cache[k])) </span> </li> <li class="alt"> <span> </span><span class="comment"><font color="#008200">#修改开始 </font></span><span> </span> </li> <li class=""> <span> </span><span class="keyword"><strong><font color="#006699">if</font></strong></span><span> </span><span class="special">self</span><span>.memcache != </span><span class="special">None</span><span>: </span> </li> <li class="alt"> <span> </span><span class="keyword"><strong><font color="#006699">if</font></strong></span><span> k </span><span class="keyword"><strong><font color="#006699">in</font></strong></span><span> </span><span class="special">self</span><span>.memcache: </span> </li> <li class=""> <span> </span><span class="keyword"><strong><font color="#006699">return</font></strong></span><span> </span><span class="special">self</span><span>.memcache[k] </span> </li> <li class="alt"> <span> </span><span class="keyword"><strong><font color="#006699">else</font></strong></span><span>: </span> </li> <li class=""> <span> </span><span class="special">self</span><span>.memcache = memcache.get(</span><span class="string"><font color="#0000ff">"sid-"</font></span><span>+</span><span class="special">self</span><span>.sid) </span> </li> <li class="alt"> <span> </span><span class="keyword"><strong><font color="#006699">if</font></strong></span><span> </span><span class="special">self</span><span>.memcache == </span><span class="special">None</span><span>: </span> </li> <li class=""> <span> memcache.set(</span><span class="string"><font color="#0000ff">"sid-"</font></span><span>+</span><span class="special">self</span><span>.sid, {</span><span class="string"><font color="#0000ff">'sid'</font></span><span>: </span><span class="special">self</span><span>.sid}, </span><span class="special">self</span><span>.session_expires) </span> </li> <li class="alt"> <span> </span><span class="special">self</span><span>.memcache = memcache.get(</span><span class="string"><font color="#0000ff">"sid-"</font></span><span>+</span><span class="special">self</span><span>.sid) </span> </li> <li class=""> <span> </span><span class="comment"><font color="#008200">#修改结束 </font></span><span> </span> </li> <li class="alt"><span> </span></li> <li class=""> <span> data = </span><span class="special">self</span><span>.get(k) </span> </li> <li class="alt"><span> ...... </span></li> <li class=""><span> </span></li> </ol>
就是做了self.memcache是否存在的判断,不存在重新添加。对于GAE的memcache的使用可以参考:《The Memcache API》。
发送email的问题
无异常发送不成功的问题。无明显异常但是发送不成功最有可能是因为mail.send_mail函数的sender不是本应用注册的那个EMAIL造成的,比如你用[email protected]注册的52jizhang.appspot.com,那么sender一定要用[email protected]否则会不成功的。
发送内容中有中文的不能发送问题。即使经过了前面数据库中中文问题的处理,发送EMAIL的中文依旧有问题,解决的方法是将发送的内容用str()包起来,:-)如下例:
<ol class="dp-py"> <li class="alt"><span><span>body = str(</span><span class="comment"><font color="#008200">"""亲爱的%s: </font></span> </span></li> <li class=""><span><span class="comment"></span> </span></li> <li class="alt"><span><span class="comment"><font color="#008200"> 您的密码重设要求已经得到验证。请点击以下链接输入您新的密码: </font></span> </span></li> <li class=""><span><span class="comment"></span> </span></li> <li class="alt"><span><span class="comment"><font color="#008200"> http://52jizhang.appspot.com/regetpassword?confirmation=%s </font></span> </span></li> <li class=""><span><span class="comment"></span> </span></li> <li class="alt"><span><span class="comment"><font color="#008200"> 如果您的email程序不支持链接点击,请将上面的地址拷贝至您的浏览器(例如IE)的地址栏进入我爱记账网。 </font></span> </span></li> <li class=""><span><span class="comment"></span> </span></li> <li class="alt"><span><span class="comment"><font color="#008200"> 感谢对我爱记账网的支持。 </font></span> </span></li> <li class=""><span><span class="comment"><font color="#008200"> 我爱记账网 http://52jizhang.appspot.com/ </font></span> </span></li> <li class="alt"><span><span class="comment"><font color="#008200"> """</font></span><span> % (userid, confirmation_url)) </span></span></li> </ol>