2007年03月26日

在这个blog看到的:

FSF is switching from Zope to Django (both Python powered!) for web application development…  Lots of new stuff coming soon, including contributions back to the django community.

Django是到处开花啊。

2007年03月24日

Tabblo是一个使用Django做的项目,它是让人们分享照片,并且进行编辑,可以打印出来做成精美的画册。HP正是看中这一点。

相关报道:

http://www.nedbatchelder.com/blog/200703.html#e20070322T091142
http://blog.tabblo.com/index.php/2007/03/22/hot-off-the-presses/
http://www.hp.com/hpinfo/newsroom/press/2007/070322a.html

虽然不是看上的django,但对于学django的人可是一个鼓舞。

具体内容见这里

简单的内容列在下面:

  1. 对于MySQLDb支持两个版本。如果在使用mysql有问题时,要么升级mysqldb为1.2.1p2或更高版本,要么修改DATABASE_ENGINE为mysql_old。更详细内容
  2. 带有一个全面的测试框架。目前它支持doctest和unittest两种测试框架。
  3. 发布newforms
  4. 对于外键所生成的限制名格式有所调整。如果报错需要进行调整。
  5. manage.py增加了dumpdata和loaddata命令,可以备份和恢复数据。它支持多种格式。还没有比较过它与我写的db_dump.py有什么异同。
  6. 在查询参数中对于反斜线的处理方式进行了调整。原来是’\\\\’现在只使用’\\’。因此在查询中用到了反斜线的话,可能要修改下条件了。(不过我没有用到过)
  7. 去掉了ENABLE_PSYCO的支持,建议写一个middleware来处理。
  8. URLconf支持导入的view方法了,以前只能是字符串形式。
2007年03月19日

这几天在学着做一个对话框,我使用的是jqModal为基础,它已经提供了显示和拖拽的基本功能(拖拽要使用jqRnD)。它的优势是代码简单,不过功能还是有限。比如拖拽,你无法限定它的最小和最大的尺寸。另外jqModal没有提供太好看的对话框效果,它只是一个框架,如果生成对话框基本上要靠你自已去写html代码和css加上图片。当然这足够灵活,也容易使用。于是我用它写了一个对话框,功能有限,有些东西是从yui-ext学来的(对我来说主要是一些css和图片了)。

那么在处理中有一个问题,在开始时我要计算一个元素的大小,然后进行调整它内部元素的大小。那么问题就来了。这个元素就是对话框div,在开始时是不显示的,计算的时候根本不对,而一旦显示计算出来的才是对的。那么这个问题如何解决呢?我看到过在interface ifx.js模块中有一段处理:

 if (t.css(‘display’) == ‘none’) {
  oldVisibility = t.css(‘visibility’);
  t.css(‘visibility’, ‘hidden’).show();
  restoreStyle = true;
 }

可以看出这里有一个技巧,它先把visibility改为’hidden’,这样这个元素是不可见的,然后执行show(),其实是修改了display为’block’。真是很有趣,两个用于显示的元素,作用不同,正好可以利用这一点来:虽然元素不可见,但是可以让它在内存中显示出来,在计算出正确的位置后,再隐藏它,然后恢复visibility为原来的值。

在ifx.js中你可以找到更全的代码。而取得宽度和高度的方法我是使用了dimensions.js来计算的。

2007年03月14日

在最初学Django时,那时Form没有生成Html代码的能力,我觉得好麻烦,于是在woodlog中写了一个easyform,它可以自动生成Form,这样我生成Form就很简单。但随着学习的深入,Django开发出了新的newforms,它可以生成多种格式的Html代码,但我却更希望手写Html代码,或希望由Javascript来生成,以减少这种自动化,并且减少处理与显示的紧密程度。真是奇怪的变化,Django从无到有,而我却从自动走向手动。当然我所说的手动也不是绝对,但是我希望的是:后台尽量只做后台的事,前台尽量只做前台的事。随着Ajax技术越来越广泛的使用,B/S模式的开发越来越象是C/S的风格,而且许多Ajax的框架的兴趣,使我们有可能在前端有越来越多动态的生成和展示,这在以前是很困难的。因此许多页面的处理效果由传统的Html的方式转为Ajax的方式,因此要想适应的,整个开发上就要分清楚,哪些是前端要做的,哪些是后端要做的。现在Django更多的是基于后端的开发,同时尽可能的使基于传统的Html的方式简化和自动化。从 Djangosnippets.com 就可以明显看出,整体网站没有一点ajax的效果,而且Form的处理完全通过newforms来生成。的确够简单,但这种风格并不是我想要的。既然我已经掌握了ajax的基本技术,它又是一个趋势,为什么我要回到简单的Html来呢?也许Ajax是麻烦些,但是只要我积累了足够的经验,开发了足够的工具,原来麻烦的东西在将来也未必还那么麻烦。

因此我一直在考虑自动化的方式。如果全部可以自动化,的确非常方便,但是在现实的情况,只要不是个人网站,涉及到多人的管理,再加上个性化的管理需求和Ajax风格的互动方式,现有的newforms和Admin功能决对是无法满足越来越复杂和越来越高品味的要求,因此这种自动化在应对这些情况时变得用处不大。而且正是因为Admin的太自动化,再加上无Ajax的支持,使得重用的可能性也非常小。修改Admin的页面绝对不是件简单的事。就好比前阵子有人在列表中讨论如何上传文件,最简单的方式就是:写一个Html的Form表单,然后提交后在request.FILES中就可以得到对应的文件属性。在最简单的情况下,你可以把这个文件写到你想要的地方。但是一旦转为Django的方式,就复杂多了。首先Html的Form没有什么特别的,复杂集中在后台。你要先在settings.py中定义MEDIA_ROOT的路径,然后一般来说你会使用数据库,因此要在数据库中定义一个FileField的字段,同时指定它的upload_to属性。而最终的文件将保存在MEDIA_ROOT/upload_to这个目录下。在保存时,Django的FileFIeld字段只保存文件名,你可以使用它提供的save_FOO_file()这个函数来保存。Django是提供了一个完整基于数据库的方式,但是对于初学者来说的确是麻烦得很。这是因为一切它已经安排好了,而按照某种安排来工作也正是框架的特点。所以学习Django这种框架,就是在学习框架的规矩和工作方式。但是在你学完之后,有些事情如果你想重用的话,一旦非常自动,就非常困难,而且由于许多自动的处理是一整套的约定,因此局部的功能可能由于不相同的环境而很难被重用。所以Admin的确不错,但是基本上无法重用。我想这可能是由于过分自动化造成的。

而我目前的确想不出如何去重用象Admin这样自动化程度过高,再加上根本没有什么Ajax的东西,所以作为给用户使用的管理界面我是不会去使用Admin的,除非只有我一个人使用,要求不高才会考虑。那么我开发了一个仅用来进行Validator处理的模块。它的定义与newforms很接近,但是它支持在newforms中不存在的validator_list参数。此后我希望它可以支持newforms中的form_from_model功能。这个功能可以从一个model生成对应的form。于是我开发了类似的功能。但是在我想应用它时我发现事情并不是很简单。因为现实是复杂的。在许多情况下,一个Model中的许多字段有着不同的作用,有些是的确需要用户提交的,有些是自动处理的,如id, 有着auto_add属性的Date字段,还有些是在保存或通过信号来处理的。因此对于用户看到的Form,并不是所有字段都需要,因此为了去掉不需要的字段,我还允许用户定义哪些字段不要,同时还可以定义哪些字段要。结果这样一来,你要非常清楚Model的定义。感觉是远不如直接手工定义更清晰,还与Model无关。这样很有可能Validator是可以重用的,而一旦与Model绑死,要与Model一起重用。所以更感觉到自动化程度高了灵活性和重用性就差了。所以自动化要么你根本不关心,要么你需要知道比手工更多的细节。

而感觉有时手工比自动更易于维护。所以目前我更趋向于手工做一些事情。当然可以通过工具来生成一些纯手工的东西,但是一定要显示而不是隐含。

2007年03月13日

 

django 中有几种URL:

  1. 用在urls.py中的
  2. 用在template中的,其中包含用于静态的URL和用态的URL(对应View方法)
  3. 用在View方法中的URL

从配置web server的角度来说,不同的URL的前缀对应于不同的处理,比如一个应用一般有一个起始的URL,然后,不同的URL前缀对应于不同的应用,有些URL前缀是对应于静态文件的。现在Zoom.Quiet在把OpenBookPlatform部署到新主机上时遇到了问题:原来在程序中为了简单,我基本上使用了绝对路径,如注册就是 ‘/login/’ 。但当真正部署到服务器上时,因为还有其它的应用,因此不能从 ‘/’ 开始映射 OpenBookPlatform功能,于是加了一个前缀,如’/platform/’,因此再使用 ‘/login/’ 就不正确了。这时有几种办法:

  1. 将前缀加进去,如’/login/’改为’/platform/login/’,从开始的说明可以看到有3个地方可能用到’/login/’,那么考虑到urls.py是web server在匹配上前缀后,才交给 Django 来解析的,所以它不受前缀的影响,只有写在template和view中的URL会受到影响,因为它们不是动态处理的。所以所有相关的地方都要进行修改。但这样改动的代码比较多。
  2. 使用相对路径。但是它只对与当前页面相关的URL是有用的,对于有些类似于全局性的URL无效,所以这种方法不能解决所有的问题。不过也说明,并不是所有的URL都需要进行修改。
  3. 还有一种可以利用MiddleWare的功能,对返回页面的所有链接进行过滤,通过设定配置规则,对于匹配上的可以修改,没匹配上的不修改,这样的好处是不用改代码了,但是可能会影响效率,所以我没有采用。当然有多大影响也不一定,同时再考虑使用Cache的话,影响会更小,所以是一个以后可以考虑的方式。

因此我采用了第一种方法对OpenBookPlatform进行修改,改动的东西是多了一些。涉及两块代码:template和view。

首先我在settings.py中增加了一个配置项:URLROOT=”。这里我设为空,在真正部署时要改为实际的前缀,如’/platform’。注意后面不要有’/',因为原来的程序都是从’/'开始的,因此不需要最后的’/'。对于view中的url修改比较简单,首先写一个helper函数,用来从settings.py中读配置项:

from django.conf import settings
def setting(name, defaultvalue=”):
    return getattr(settings, name, defaultvalue)

然后在需要修改URL的地方加上:

setting(‘URLROOT’) + ‘/login/’

这样就可以了。

对于template的修改分为三种情况。对于静态文件的URL,不用修改,继续使用如/site_media/img/xxx 这样的URL,因为可以通过配置web server的URL映射规则来进行处理,所以不用修改。那么要修改就是针对于动态的View方法的URL,有相对路径的和绝对路径的。为了可以在template中使用settings信息,可以通过设置TEMPLATE_CONTEXT_PROCESSORS来增加一个ContextProcessor的处理函数将settings对象导入,如:

TEMPLATE_CONTEXT_PROCESSORS = (
    ‘django.core.context_processors.auth’,
    ‘django.core.context_processors.debug’,
    ‘django.core.context_processors.i18n’,
    ‘django.core.context_processors.request’,
    ‘utils.setting_process.settings’,
)

然后编写一个简单的ContextProcessor的函数:

from django.conf import settings as _settings

def settings(request):
    return {’settings’: _settings}

这样就可以在template中使用settings对象了。

对于绝对路径,在绝对路径前加上:{{ settings.URLROOT }}/login/ 就可以了。对于相对路径则可以不作修改。

经过上面的修改,当部署应用时有前缀,只要把 settings.py 中的URLROOT 修改一下就可以了。

不过上述只是考虑了整个应用有前缀的情况,并没有考虑某个App功能的前缀变化的问题。在woodlog项目中其实是考虑了这一点。它把所以App的前缀都放在settings.py进行配置了,如:

APP_ROOT = {’blog’:'blog’, ’setting’:’setting’, ‘tags’:'tags’, ‘digest’:'digest’, ‘rss’:'rss’}

这样在处理view和template时还要考虑URL是哪个app的,然后从settings.py中得到相应的配置值。不过这里比较简单,就没有考虑。

2007年03月12日

其实在邮件列表中已经有不少是台湾的朋友,虽然我不知道很确切都是谁,不过大概知道几个,象gasolin就是一个,他非常喜欢 TurboGears ,而且提交了不少的功能,在大陆这边的邮件列表也是很活跃的分子。今天gasolin说是台湾的planet已经正式运作,希望有更多的中文的Python Bloger加入,我很感兴趣,于是就说了一句想加入的话。gasolin告诉我他不是管理员要我和管理员联系。不过一天都比较忙,这件事就忘到脑后去了。结果晚上收到 planet.tw 管理员timchen发来的邮件,问我为什么没有发邮件,是不是邮件丢了。于是我解释了一下,tim chen已经知道了我的rss feed,于是我只发了一张照片(50*50)的过去,很快我的rss feed就出现在了planet上面,速度很快。这个planet可是一个Django项目哦!

并且tim chen还写了一篇blog,我很快也看到了。感谢!

有兴趣的Python朋友不妨也加入,让我们大家一起分享经验,快乐,很有趣的事情。

2007年03月06日

今天找到一个ChartDirector的非常不错的生成Chart图的库,它生成的结果是图片。它是使用C++开发的,同时支持多种语言的绑定,可以在象Python这样在的动态语言中使用。除了它,我还找到一个叫XML/SWF的Falsh的生成Chart图的东西,从名字可以看出,它是使用XML作为数据交换的格式。它与前者不同,通过SWF可以实现动态的图效果,还可以实时变化。不过考虑到它的体积和下载速度,我还是先考虑如何使用ChartDirector吧。下面是简单的使用方法。

一、安装

ChartDirector支持多种平台,我是在Windows下测试的。下载ChartDirector后,我没有发现有标准的安装程序,文档中的安排说明就是手工来做。于是我在Lib/site-packages下创建了一个chartdir的目录,然后将lib目录下的文件拷贝到这个目录下。再在site-packages目录下创建了名为chartdir.pth的文件,内容就一行:chartdir,它就是chartdir目录名。这样就可以方便地导入pychartdir库了。

二、测试

ChartDirector提供了一个chm格式的文档,写得非常详细,有许多的例子,还有生成的效果图。而且ChartDirector本身也带了许多的例子程序可以直接运行测试的。例子分为两种,一种是生成图形文件,另一种是在CGI下运行,输出字节流的。因此你可以简单地运行第一类的例子,直接生成图片看结果进行测试。很简单。

三、在Django中进行测试

在第二类的例子中,它为了生成二进制数据,代码如:

print "Content-type: image/png\n"
binaryPrint(c.makeChart2(PNG))

第一行是输出结果的Content-type,第二行是输出二进制的结果。在文档上说明,print会对回车进行转换,并且输出多余的回车。不过这是在传统的CGI方式下才有的,在Django下,提供了HttpResponse可以很好的处理MIME类型和二进制数据,如:

return HttpResponse(c.makeChart2(PNG), ‘image/png’)

同时要注意,这里的makeChart2后面有个2,而且参数是输出图片的格式,与直接生成文件的方法不一样。那么View方法只要最后象上面就行了。其它的代码可以直接从例子中拷贝就行了。

四、中文处理

中文是非常重要的一点。而ChartDirector很好的支持了unicode,你可以使用utf-8编码的程序,在程序前加上:#coding=utf-8,并且保存程序为utf-8编码。有些方法提供对字体的设置,如addTitle(‘中文’, ’simsun.ttc’, 15),第二个参数就是字体文件(simsun.ttc就是宋体字体的文件名),它不使用字体名称。那么如果你想整个字体都为中文字体的话,可以使用:setDefaultFonts(’simsun.ttc’),这样整个图都使用宋体了。

五、版权

这个库本身是有版权的,但你可以在不交钱的情况下使用,不过对于无版权的使用,它会自动在图形底部生成一个黄色的信息条。如果你感得它有用,可以考虑注册,如果象我一样还不想花钱,那么有就有吧。

django 已经内置了用户认证的一整套处理,但总不可能满足所有用户的要求,因此在许多情况下我们需要对User进行扩展。这里有一篇不错的Blog,讲得比较详细。这篇Blog是使用django提供的userprofile扩展机制,要点如下:

  1. 在settings.py中设置要使用的UserProfile Model 信息,如:

    AUTH_PROFILE_MODULE = ‘myapp.UserProfile’

    注意,后面的字符串只能有两项,第一个是app的名字,第二个是Model的名字,其它的情况会报错。

  2. UserProfile表与User之间建议使用ForeignKey,并且key为不重复,示例如下:

    class UserProfile(models.Model):
        portrait = models.ImageField(upload_to=’photo’, blank=True)
        user = models.ForeignKey(User, unique=True)

  3. 然后在使用时,先得到user,然后通过user提供的get_profile()来得到profile对象,如:

    user.get_profile().portrait

使用get_profile的好处是移植更方便,而且在User中对get_profile的调用可以有cache处理。原本我想到是否这样当我修改了profile信息之后,是不是会有不同步的问题出现呢(因为我查了User的代码,没有cache取消的处理)。因此还在django邮件列表中发了一封邮件。不过后来想想,当一个view处理时,是不会修改profile的。但修改了profile,再得到User时应该是一个新的对象。而User的cache的处理是放在User对象的属性中,一旦产生新的User对象,原cache就无效了。因此使用cache应该没有太大的问题,而且因为get_profile()是一个方法,因此使用cache才更象以前的OneToOne的关系方式。

不过在get_profile中我并没有看到如何创建这个profile的过程,Blog中也没有讲。在我的处理中我是当用户访问与User相关的链接时,会自动判断是否存在profile信息,如果没有就创建。原来是写为decorator的,这样需要放置的地方比较多,感觉麻烦,后来在新开发的Url Filter Middleware之上,与用户权限检查合并在了一起,这样就可以不用考虑decorator了。方便多了。对应的filter函数为:

from django.contrib.auth.models import User
from utils.common import render_template
from apps.users.models import UserProfile

def check_valid_user(request, user_id):
    if request.user.is_anonymous():
        return render_template(request, ‘users/user_login.html’, {’next’:'%s’ % request.path})
    try:
        person = User.objects.get(pk=int(user_id))
    except User.DoesNotExist:
        return render_template(request, ‘error.html’, {’message’:_("User ID (%s) is not existed!") % user_id})
    try:
        profile = person.get_profile()
    except:
        #if there is no profile record existed, then create a new record first
        profile = UserProfile(user=person)
        profile.save()
    if person.id != request.user.id:
        return render_template(request, ‘error.html’, {’message’:_(‘You have no right to view the page!’)})

2007年03月04日

今天又完成了昨天想到的一个Middleware,叫FilterMiddleware。原本是想写一个用于用户检查的Middleware,但是后来发现还可以通用化,就写了这个FilterMiddleware。

基本思想是这样的。首先说一下用户检查。目前我需要的用户检查基本上是:

  1. 用户需要登录,如果没有登录则显示一个登录窗口
  2. 要访问的用户ID是否存在,不存在则显示一个出错信息
  3. 登录用户访问的链接中的用户ID是否与自已的ID相符,如不相符则报错,相符则进入页面

在 OpenBookPlatform 中,最早是使用decorator来实现的,在urls.py中是可以单独处理,但是与用户有关的链接很多,结果一方面要把导入的函数从字符串改为函数对象的形式(就是去掉前后的引号),另一方面要加上decorator的处理,比如:

import apps.users.views.views
import decorator.check_valid_user

urlpatterns = patterns(”,
    ….
    (r’^user/(?P<object_id>\d+)/$’, decorator.check_valid_user(apps.users.views.views.user_detail)),
    ….

可以看到每个url都要增加decoratorcheck_valid_user的处理,链接多了很麻烦。因此我想通过Middleware能否对相同形式的r’^user/(?P<user_id>\d+)/’进行统一处理呢?于是想通过处理process_request来首先对request.path进行过滤,然后如果满足过滤条件,则进行用户的检查处理。理论上是可行的。

在考虑到URL的过滤处理上,再想可以通用化,也许除了用户信息处理还可以过滤些别的URL,因此做成了通过的FilterMiddleware。这样在使用它时需要配置,具体的在 djangosnippets.com 中有说明,这里不再详述了。同时在 OpenBookPlatform 的源码中有示例。分别在 settings.py 中的 FILTERS中进行配置。在 MIDDLEWARE_CLASSES 中增加这个中间件。在users/filter.py中编写了 check_valid_user() 函数。

有兴趣的可以用它来简化一些有规律的url的批量处理。