罗孚传说

RoverTang.com

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

上一篇我的第一个GAE网站(地图+相册)已经讲了大体的思路,此篇讲讲实现过程中的一些技术要点,可能讲的比较零散,因为整个过程中碰到了比较多的问题,但最终还是解决了,将这过程中的经验分享给大家。

#Picasa Web Albums Data API的二次开发

其实Picasa Web Albums Data API的二次开发还是非常简单的,细看pwa.js文件(SF上的一个开源的项目,用一个js实现了picasa web albums的全部调用),也就几十行的代码,大致分成显示所有的相册(picasaweb函数)、显示相册内的相片(albums函数)、显示一张照片这几个函数(photo函数),其余的都是辅助函数,那么如何调用和触发的呢。触发的语句就是调用不同的JS,下面这三行是整个JS的开始:

if (_GET['albumid']) {
    _$('<script type="text/javascript" src="http://picasaweb.google.com/data/feed/base/user/' + username + '/albumid/' + _GET['albumid'] + '?category=photo&alt=json&callback=browsePhoto"></script>');
} else {
    _$('<script type="text/javascript" src="http://picasaweb.google.com/data/feed/base/user/' + username + '?category=album&alt=json&callback=picasaweb&access=public"></script>');}

这是根据当前URL中albumid的值来判断并调用不同的JS,如果URL中包含albumid则显示相册中的所有照片,如果没有albumid那么就显示所有相册列表。由于pwa.js提供的相册列表的URL链接不满意,所以我准备重写所有的相册的URL,以让其符合我的要求。这里需要做一个解释的地方是:script中src所带的callback参数,其实就是回调函数,在调用完JS后就交给callback的参数(其实就是function名)来处理了。上面的browsePhoto函数就是用于显示相册中照片列表或者显示单独一张较大尺寸的照片,但也是用了if语句来转向处理再来调用albums函数或者photo函数。具体细节可以看相应的函数,重要的是了解picasa web api返回的数据,是带了许多分支节点的JS,但调用非常的简单,j.feed.entry[i],j就是调用回来的所有数据,feed是总的开始,如同xml中的xml总结点,而entry是一个一个的对象,entry更有细小的对象,具体可以下载一个JS下来分析一下就可以了。

所以相册的整体思路就是相册列表由我自己完成,而相册中照片列表和单张照片显示则使用pwa.js来完成。由JS调用的URL上能够看出,我只要手动告诉它该相册的ID就可以调用并显示相册中的照片列表了,而自动获取ID的方式我也无法知道对应的是哪个相册,这样就URL也不好管理了,而且Google的相册名称也不是我要的英文名,而是一个乱七八糟的名字,失望。

由此,我需要将自己的URL路径名称和相册的ID同时存放到数据库中,然后读取出来就可以对应。

#数据结构和数据上传

由于需求简单,所以对于我来说数据结构也是超级简单。而虽然说datastore是非关系型数据库(简单来说就是一个excel表,只是比excel高级一些),但对于我来说已经足够用了,并且我就喜欢简单的东西,不希望复杂。而我的数据结构也确实非常的简单,根据上面的情况,以及显示地图的需要,所以基本上定义为纬度、经度、城市、英文名、中文名、信息、相册ID这几个字段。在GAE中的定义为:

class PointList(db.Model):
    Lat = db.StringProperty()
    Lon = db.StringProperty()
    AlbumID = db.StringProperty()
    City = db.StringProperty()
    NameEn= db.StringProperty()
    Name = db.StringProperty()
    Information = db.StringProperty()

这就表示已经设置好数据组织格式了。如果上传了数据,那么就能看到PointList,就是相当于一个数据表了。

由于46笔数据,虽然说不多,但我也不希望自己一个一个录入吧,所以首先整理成一份CSV的数据表格,然后批量上传到GAE或者本地开发环境上。由于GAE不支持中文,如果将带有中文的CSV文件上传到GAE上则无法上传。查了非常多的资料以后,终于根据K_ReVerTer的资料将数据上传上去了,关于此点我还特地写了一文:将中文csv数据上传到GAE Datastore(Bulk Data Uploader工具),不过K_ReVerTer又发现了新的上传方式,我还没有研究过,大家可以一起看看:向Google App Engine上传数据的几个心得(上)。此处就不再多说。

#GAE中定义URL以及URL转向

一般情况下大家都认为定义URL是app.yaml的事情,没错它确实是可以定义URL,比如:

- url: /static
  static_dir: static

- url: /admin/.*
  script: admin.py

- url: /.*
  script: main.py

上面举例了static静态文件夹的url,而admin/后的内容全部交由admin.py来处理,而直接域名或无法匹配到上面内容的则最终交给main.py来处理,顺便说一下这是的处理按顺序处理的,如果没有任何对应则出现404错误。

但我总不能将我的46个URL全部用app.yaml来对应吧,要累死人的,并且也不利于以后的添加。最后就交给main.py来处理了:

application = webapp.WSGIApplication([('/', MainPage),
                                        ('/photo/', AlbumPage),
                                        ('/shanghai/(.*)/', PhotoPage),
                                        ('/(.*)/', ReURLPage),
                                        ],
                                        debug=True)

这里的大致意思是域名直接访问则交给MainPage处理,如果是/photo/路径则交给AlbumPage处理,而PhotoPage和ReURLPage则需要处理(.*),其实就是表示一个字符串。举实际的例子来说吧,外滩使用theBund的路径,如果出现/theBund/则跳转到/shanghai/theBund/的路径,ReURLPage负责跳转,PhotoPage负责显示照片。大致明白我的意思了吧。

顺便说一下URL转向,最早的时候我用了比较笨的方法,但也可以达到最终目的,我在py中写到:self.response.out.write('<html><head>
<meta http-equiv="refresh" content="0;url=/shanghai/theBund/" />
<title>ShareSh.cn</title><body>Loading...</body></head></html>'),其实就是用meta的refresh方法。

然而不经意间,竟然发现一个GAE还能self.redirect("/shanghai/theBund/"),redirect直接URL跳转,还是蛮好用的。

#数据读取和使用静态模板文件

其实在GAE的入门教程中已经写了如何使用模板文件的例子,不过粗略看过以后是不知所云的,最终在碰到问题的时候才印象深刻。首先写一个html的文件,内容你已经组织好,那么如何使用python写一些内容到这个html文件中呢,比如title你总想要一个名称吧,总不能固定不变吧,而对于像Google map的调用方面,你也肯定需要加载许多的对象吧。OK,我们的静态文件中只要加入“{{key}}”就可以了,这个key你可以随便自定义,可以放于多个地方,比如我的静态文件中的title标题:<title>{{photoname}}--分享上海北京(ShareSh.cn)</title>,其中photoname就是我在py中会提供给html文件,同时对于数据库中的多个数据,还可以循环,比如我显示图片和链接的例子:

{% for point in points %}
<a href="/{{point.City}}/{{point.Nameen}}/" title="{{point.Nameen}}">{{point.Name}}</a>
{% endfor %}

points是select datastore后的对象,而point是在此定义的单独的对象,而point.Name则是对应一条记录中Name字段的值。真是没有想到python是如此简单就将复杂的东西全部表现出来了,这是我非常喜欢的地方。

而关于数据库就非常的简单了,看我具体的例子:

class AlbumPage(webapp.RequestHandler):
    def get(self):
        points = db.GqlQuery("SELECT * FROM PointList")
        path = os.path.join(os.path.dirname(__file__), 'album.html')
        tvalues = {
            'points': points
            }
        self.response.out.write(template.render(path, tvalues))

points就是select后的结果,album.html就是上文的模板文件,由此也就明白了py是如何将数据传给静态模板文件的了吧。

select语句还可以使用where查询,比如:albums = db.GqlQuery("SELECT * FROM PointList WHERE Nameen= :1", id),PointList是表,Nameen是字段,id是外部的值。

其实我的Gmap的显示也是用了py的数据库循环,因为就是定义一个marker然后addoverlay,循环46次就可以标识出所有的marker了。

#多个域名下调用Google Maps API不同的key

由于GAE自身一个域名:shareshcn.appspot.com,我自己又定义了一个域名:photo.sharesh.cn,这样的话Gmap的key我就有两个了,但script语句只能一个key:<script src="http://maps.google.com/maps?file=api&v=2&key=yourdomainkey type="text/javascript"></script>,怎么办?对于GAE来说,个人认为解决的思路有三种:

1,如我在Google Maps API离线开发包(没有网络也可以开发Gmap了)一文中所说,先申请一个不是这两个域名的key,然后下载src对应的文件,定义为maps.js,去掉其中的GValidateKey这个判断前的!,不再提示,然后你在网站中就直接调用自己maps.js就可以了,不用去调用Google的src。

此方法理论上可行,我并没有尝试,可以说此方法不再受多少个域名限制,但是个人担心Google再验证或者js无法更新等问题,我不使用此方法。

2,使用JS判断URL,然后调用不同的script:

if (window.location.href.indexOf('yourdomain')>=0){document.write("<script src=\"http://maps.google.com/maps?file=api&v=2&key=yourdomainkey type=\"text/javascript\"></script>");}

我认为是可行的,但在实际使用过程中不可行,难道是GAE的特殊性?或者和调用的先后顺序有关?我的这个js判断是在body的load之前的啊,是在所有内容之前的啊。没有办法,我再度放弃这个方法。

3,使用GAE的模板模式,并用python判断Host:

这个思路也是非常明确的,首先在html文件中定义好关键字,比如我的:<script src="http://maps.google.com/maps?file=api&v=2&key={{gmapkey}}" type="text/javascript"></script>

然后我在py中判断不同的域名来提供不同的Google maps key。具体写法如下:

class MainPage(webapp.RequestHandler):
    def get(self):
        if self.request.headers["Host"] == "yourdomain":
            gmapkey = "yourdomainkey"
        if self.request.headers["Host"] == "yourdomain2":
            gmapkey = "yourdomain2key"
        points = db.GqlQuery("SELECT * FROM PointList")
        path = os.path.join(os.path.dirname(__file__), 'main.html')
        tvalues = {
            'gmapkey': gmapkey,
            'points': points
            }
        self.response.out.write(template.render(path, tvalues))

要知道self.request.headers["Host"] 这个我也是摸索了半天才出来的,python中如何获取当前域名,就是用这个方法来实现的,网上找了一些资料没有找到,并且还走了一些弯路,我用print self.request.headers["Host"] 准备打印在页面上判断这种用法是正确的,但不知道为什么GAE就是不显示,其余的像Content-Type参数就是可以打印显示出来的,郁闷吧,但你用Host来逻辑运算是没有问题的,所以我尝试了一下if判断,然后print自己的字符串,发现可用,由此我才知道Host无法直接打印出来但可以逻辑运算的,虽然花的时间并不是太多,但这确实有点弯路,看来没有学习过python还真不容易。

最终来说,使用python判断不同URL并调用不同的JS是完全正确的,并且也是非常有效的,因为此点首先就是在服务器上生成的html,不存在时间先后问题。如果大家有更好的不同域名调用gmap key的方法,不妨告知一下。

洋洋洒洒又是好几千字,写了五个方面的要点,也许每个要点都能单独写个文,不过太小的事情总不是很愿意写,比如本来想推荐一下picasa的自定义模板输出功能的,但没太多的话说,所以一直没说。本来把名字写成是零散,最后改成了凌乱,确实很凌乱,可能我意思明确,但写出来,自己感觉都是不知所云,罢,就这么凌乱吧。

posted on 2009-03-15 15:20  Rover.Tang  阅读(1631)  评论(0编辑  收藏  举报