2010年8月25日星期三

两份文本

原文授权都是public domain

Bible (King James)/Genesis
Chapter 1

1 In the beginning God created the heaven and the earth.
2 And the earth was without form, and void; and darkness was upon the face of the deep. And the Spirit of God moved upon the face of the waters.
3 And God said, Let there be light: and there was light.
4 And God saw the light, that it was good: and God divided the light from the darkness.
5 And God called the light Day, and the darkness he called Night. And the evening and the morning were the first day.
6 And God said, Let there be a firmament in the midst of the waters, and let it divide the waters from the waters.
7 And God made the firmament, and divided the waters which were under the firmament from the waters which were above the firmament: and it was so.
8 And God called the firmament Heaven. And the evening and the morning were the second day.
9 And God said, Let the waters under the heaven be gathered together unto one place, and let the dry land appear: and it was so.
10 And God called the dry land Earth; and the gathering together of the waters called he Seas: and God saw that it was good.
11 And God said, Let the earth bring forth grass, the herb yielding seed, and the fruit tree yielding fruit after his kind, whose seed is in itself, upon the earth: and it was so.
12 And the earth brought forth grass, and herb yielding seed after his kind, and the tree yielding fruit, whose seed was in itself, after his kind: and God saw that it was good.
13 And the evening and the morning were the third day.
14 And God said, Let there be lights in the firmament of the heaven to divide the day from the night; and let them be for signs, and for seasons, and for days, and years:
15 And let them be for lights in the firmament of the heaven to give light upon the earth: and it was so.
16 And God made two great lights; the greater light to rule the day, and the lesser light to rule the night: he made the stars also.
17 And God set them in the firmament of the heaven to give light upon the earth,
18 And to rule over the day and over the night, and to divide the light from the darkness: and God saw that it was good.
19 And the evening and the morning were the fourth day.
20 And God said, Let the waters bring forth abundantly the moving creature that hath life, and fowl that may fly above the earth in the open firmament of heaven.
21 And God created great whales, and every living creature that moveth, which the waters brought forth abundantly, after their kind, and every winged fowl after his kind: and God saw that it was good.
22 And God blessed them, saying, Be fruitful, and multiply, and fill the waters in the seas, and let fowl multiply in the earth.
23 And the evening and the morning were the fifth day.
24 And God said, Let the earth bring forth the living creature after his kind, cattle, and creeping thing, and beast of the earth after his kind: and it was so.
25 And God made the beast of the earth after his kind, and cattle after their kind, and every thing that creepeth upon the earth after his kind: and God saw that it was good.
26 And God said, Let us make man in our image, after our likeness: and let them have dominion over the fish of the sea, and over the fowl of the air, and over the cattle, and over all the earth, and over every creeping thing that creepeth upon the earth.
27 So God created man in his own image, in the image of God created he him; male and female created he them.
28 And God blessed them, and God said unto them, Be fruitful, and multiply, and replenish the earth, and subdue it: and have dominion over the fish of the sea, and over the fowl of the air, and over every living thing that moveth upon the earth.
29 And God said, Behold, I have given you every herb bearing seed, which is upon the face of all the earth, and every tree, in the which is the fruit of a tree yielding seed; to you it shall be for meat.
30 And to every beast of the earth, and to every fowl of the air, and to every thing that creepeth upon the earth, wherein there is life, I have given every green herb for meat: and it was so.
31 And God saw every thing that he had made, and, behold, it was very good. And the evening and the morning were the sixth day.


United States Declaration of Independence
In CONGRESS, July 4, 1776.
A DECLARATION
By the REPRESENTATIVES of the
UNITED STATES OF AMERICA,
In GENERAL CONGRESS assembled.
WHEN in the course of human Events, it becomes necessary for one People to dissolve the Political Bands which have connected them with another, and to assume among the Powers of the Earth, the separate and equal Station to which the Laws of Nature and of Nature’s God entitle them, a decent Respect to the Opinions of Mankind requires that they should declare the causes which impel them to the Separation.
We hold these Truths to be self-evident, that all Men are created equal, that they are endowed by their Creator with certain unalienable Rights, that among these are Life, Liberty, and the pursuit of Happiness—-That to secure these Rights, Governments are instituted among Men, deriving their just Powers from the Consent of the Governed, that whenever any Form of Government becomes destructive of these Ends, it is the Right of the People to alter or abolish it, and to institute a new Government, laying its Foundation on such Principles, and organizing its Powers in such Form, as to them shall seem most likely to effect their Safety and Happiness. Prudence, indeed, will dictate that Governments long established should not be changed for light and transient Causes; and accordingly all Experience hath shewn, that Mankind are more disposed to suffer, while Evils are sufferable, than to right themselves by abolishing the Forms to which they are accustomed. But when a long Train of Abuses and Usurpations, pursuing invariably the same Object, evinces a Design to reduce them under absolute Despotism, it is their Right, it is their Duty, to throw off such Government, and to provide new Guards for their future Security. Such has been the patient Sufferance of these Colonies; and such is now the Necessity which constrains them to alter their former Systems of Government. The History of the Present King of Great-Britain is a History of repeated Injuries and Usurpations, all having in direct Object the Establishment of an absolute Tyranny over these States. To prove this, let Facts be submitted to a candid World.
He has refused his Assent to Laws, the most wholesome and necessary for the public Good.
He has forbidden his Governors to pass Laws of immediate and pressing Importance, unless suspended in their Operation till his Assent should be obtained; and when so suspended, he has utterly neglected to attend to them.
He has refused to pass other Laws for the Accommodation of large Districts of People; unless those People would relinquish the Right of Representation in the Legislature, a Right inestimable to them, and formidable to Tyrants only.
He has called together Legislative Bodies at Places unusual, uncomfortable, and distant from the Depository of their public Records, for the sole Purpose of fatiguing them into Compliance with his Measures.
He has dissolved Representative Houses repeatedly, for opposing with manly Firmness his Invasions on the Rights of the People.
He has refused for a long Time, after such Dissolutions, to cause others to be elected; whereby the Legislative Powers, incapable of Annihilation, have returned to the People at large for their exercise; the State remaining in the mean time exposed to all the Dangers of Invasion from without, and Convulsions within.
He has endeavoured to prevent the Population of these States; for that Purpose obstructing the Laws for Naturalization of Foreigners; refusing to pass others to encourage their Migrations hither, and raising the Conditions of new Appropriations of Lands.
He has obstructed the Administration of Justice, by refusing his Assent to Laws for establishing Judiciary Powers.
He has made Judges dependent on his Will alone, for the Tenure of their Offices, and Amount and Payment of their Salaries.
He has erected a Multitude of new Offices, and sent hither Swarms of Officers to harass our People, and eat out their Substance.
He has kept among us, in Times of Peace, Standing Armies, without the consent of our Legislature.
He has affected to render the Military independent of and superior to the Civil Power.
He has combined with others to subject us to a Jurisdiction foreign to our Constitution, and unacknowledged by our Laws; giving his Assent to their Acts of pretended Legislation:
For quartering large Bodies of Armed Troops among us:
For protecting them, by a mock Trial, from Punishment for any Murders which they should commit on the Inhabitants of these States:
For cutting off our Trade with all Parts of the World:
For imposing taxes on us without our Consent:
For depriving us, in many Cases, of the Benefits of Trial by Jury:
For transporting us beyond Seas to be tried for pretended Offences:
For abolishing the free System of English Laws in a neighbouring Province, establishing therein an arbitrary Government, and enlarging its Boundaries, so as to render it at once an Example and fit Instrument for introducing the same absolute Rule in these Colonies:
For taking away our Charters, abolishing our most valuable Laws, and altering fundamentally the Forms of our Governments:
For suspending our own Legislatures, and declaring themselves invested with Powers to legislate for us in all Cases whatsoever.
He has abdicated Government here, by declaring us out of his Protection and waging War against us.
He has plundered our Seas, ravaged our Coasts, burnt our Towns, and destroyed the Lives of our People.
He is, at this Time, transporting large Armies of foreign Mercenaries to compleat the Works of Death, Desolation, and Tyranny, already begun with circumstances of Cruelty and Perfidy, scarcely paralleled in the most barbarous Ages, and totally unworthy the Head of a civilized Nation.
He has constrained our fellow Citizens taken Captive on the high Seas to bear Arms against their Country, to become the Executioners of their Friends and Brethren, or to fall themselves by their Hands.
He has excited domestic Insurrections among us, and has endeavoured to bring on the Inhabitants of our Frontiers, the merciless Indian Savages, whose known Rule of Warfare, is an undistinguished Destruction, of all Ages, Sexes and Conditions.
In every stage of these Oppressions we have Petitioned for Redress in the most humble Terms: Our repeated Petitions have been answered only by repeated Injury. A Prince, whose Character is thus marked by every act which may define a Tyrant, is unfit to be the Ruler of a free People.
Nor have we been wanting in Attentions to our British Brethren. We have warned them from Time to Time of Attempts by their Legislature to extend an unwarrantable Jurisdiction over us. We have reminded them of the Circumstances of our Emigration and Settlement here. We have appealed to their native Justice and Magnanimity, and we have conjured them by the Ties of our common Kindred to disavow these Usurpations, which, would inevitably interrupt our Connections and Correspondence. They too have been deaf to the Voice of Justice and of Consanguinity. We must, therefore, acquiesce in the Necessity, which denounces our Separation, and hold them, as we hold the rest of Mankind, Enemies in War, in Peace, Friends.
We, therefore, the Representatives of the UNITED STATES OF AMERICA, in General Congress, Assembled, appealing to the Supreme Judge of the World for the Rectitude of our Intentions, do, in the Name, and by the Authority of the good People of these Colonies, solemnly Publish and Declare, That these United Colonies are, and of Right ought to be, Free and Independent States; that they are absolved from all Allegiance to the British Crown, and that all political Connection between them and the State of Great-Britain, is and ought to be totally dissolved; and that as Free and Independent States, they have full Power to levy War, conclude Peace, contract Alliances, establish Commerce, and to do all other Acts and Things which Independent States may of right do. And for the support of this Declaration, with a firm Reliance on the Protection of the divine Providence, we mutually pledge to each other our Lives, our Fortunes, and our sacred Honor.
Signed by Order and in Behalf of the Congress,
JOHN HANCOCK, President.
Attest.
CHARLES THOMSON, Secretary.
Philadephia: Printed by John Dunlap.


读起来都很有感觉的样子,比如说是从逻辑上坚信着什么的并将之表达出来,而成为构成人身体之外的若干要素,并使得以判断力去支持人去做出些什么。

2010年8月22日星期日

ORM & Serialization in Python

这篇依旧是导读性质的一篇,在看Python自带文档缺少头绪的时候可以参考,不然就是在被误导了哦。
而且其实呢,闲扯才是这篇的真正目的呢,虽然说貌似没什么想说的,唉还是在胡言乱语来着的。

之所以要****,是为了存储或传输。

Qt中使用的方式是通过流来实现的,提供x,x方法,并通过重载<<以及>>运算符来实现接口。

虽然标题什么的是已经确定了话题的方向,不过下面的内容只是在随意地跑跑而已。


* import

直接导入py文件或pyc文件,适合存放开发中的自定义数据。


* pickle

用于Python对象和数据流见的转化,用于数据持久化
最简单的示例是:
pickle.dump(data, open('save.p', 'wb'))
reader = pickle.load(open('save.p', 'rb'))
data是需要保存的类型,通常是个字典
可以往一个file对象dump/load多次
pickle的dumps和loads则是用字符串代替文件
对于自己定义类型的实例,可以提供__getstate__/__dict__以及__setstate__
其保存的数据格式会有不同版本,不过文件是保持向后兼容的
似乎pickle是Py的类似模块中最通用的选择,即处于比较暴露的接口
关联的模块marshal和shelve有不同的用法。

试验代码:
#+BEGIN_SRC python
import pickle
class MyObj1:
     def __init__(self,value):
        self.value=value
class MyObj2:
     def __init__(self,value):
        self.value=value
     def __getstate__(self):
        return self.__dict__.copy()
     def __setstate__(self,dict):
        self.__dict__.update(dict)
m1 = MyObj(6)
m2 = MyObj2(7)
data1 = {'str':"string",0:"zero",1:m1,2:m2}
save = pickle.dumps(data1)
data2 = pickle.loads(save)
print data1[2].value == data2[2].value
#+END_SRC


* json

Python中实现json编解码的模块提供了类似pickle的接口,提供load和dump方法。
具体的编解码操作由JSONEncoder/JSONDecoder提供,可以通过派生以cls参数使用或者添加hook的方式来添加对非内置对象实例的操作方法。

试验代码:
#+BEGIN_SRC python
import json
data1 = {0:1,1:2,2:3}
print json.dumps(data1,sort_keys=True)
def myhook(dct):
     return {"vector":"|".join(map(str,dct["vector"]))}
data2 = json.loads("""
{"vector":[1,2]}
""",object_hook=myhook)
print data2

class MyEncoder(json.JSONEncoder):
     def default(self, obj):
        if isinstance(obj, complex):
         return [obj.real, obj.imag]
        return json.JSONEncoder.default(self, obj)
print MyEncoder().encode(1+4j)
#+END_SRC


* XML

Python中和XML相关的实现且在文档中提供实例的有几个:
HTMLParser通过实现handle方法
xmlrpclib/SimpleXMLRPCServer这个用起来和XML感觉不大
xml.dom.minidom实现了简单的DOM接口
或者用SAX的接口的实现更好的解析性能
关于DOM与SAX的规范的内容并不在Py的范畴内
xml.parsers.expat亦有足够悠久的历史
xml.etree.ElementTree则是Py2.5中新增的
各自用法还算有明显的区别的,算是明显吧


* sqlite3

用sqlite3.connect创建Connection对象,可以文件可以":memory:",提供cursor方法获得Cursor对象,然后就找到我们的execute方法了。
可用“?”占位传入值,执行“select”后可对Cursor对象迭代,或者用Cursor对象的若干fetch方法来获取Row的数据。
事务方面的操作在Connection对象上。
可自定义converter来实现sqlite数据和Python对象的转化。

试验代码:
#+BEGIN_SRC python
import sqlite3
conn = sqlite3.connect(":memory:")
c = conn.cursor()
c.execute("create table name(key,value)")
c.execute('insert into name values (?,?)',("hello","world"))
conn.commit()
c.execute('select * from name order by key')
for row in c:
     print row
c.close()
#+END_SRC


* ConfigParser

用来读写ini格式的文件。
虽然是源于Windows的格式,Linux下还是有程序在用的。


* csv

用于读写CSV格式的文件,通过对文件建立reader和writer对象。


* codecs

用于读写UTF-8编码的文件,提供了open的同名函数


* StringIO

实现内存中的文件对象,接口按照对文件的方式使用,提供getvalue方法获取值。


* ZODB

Zope Object Database,就是Zope对象数据库的意思,不过可以作为独立的模块使用。
和Django开发中支持的MongoDB类似的,都属于NoSql的对象数据库,不过ZODB对Py有Native的感觉。
安装“easy_install ZODB3”,导入“import ZODB”,然后便可使用。
使用的接口类似于shelve模块(都默认仅在__setitem__之类的操作时探测到更新),
也就是说是依然dict对象提供的接口的方式使用一个root对象。
因为这里不去涉及ZOPE,所以也仅写一段just work的代码来试一试。

试验代码:
#+BEGIN_SRC python
from ZODB.FileStorage import FileStorage
from ZODB.DB import DB
from persistent import Persistent
class One(Persistent):
     def __init__(self):
        self.todo=[]
     def add(self,task):
        self.todo.append(task)
        self._p_changed = 1
storage = FileStorage('Data.fs')
db = DB(storage)
connection = db.open()
root = connection.root()
root['main']=["hello","world"]
import transaction
transaction.commit()
print root.items()
connection.close()
db.close()
#+END_SRC


* Django-norel

是一个让Django在NoSql数据库上模拟出Django原本的数据Model的查询的Django分支,在Google App Engine上写应用时在用。



* SQLAlchemy

对象关系映射,也就是通常缩写为ORM(Object-relational mapping)的东西。
使用了ORM可以通过OO的方式方便使用数据库,并可方便的切换所使用的数据库后端。
SQLAlchemy虽然才在2006年发布了他的第一个版本,但是很快成为Python社区使用最广泛的ORM模块。
和Django的ORM模块作用是一样的,只不过Django的Model类一般只在Django应用里使用,而且不及SQLAlchemy复杂(至少不是只倾向暴露给用户一个最高层的接口)。
TurboGears的话,是目前尝试使用SQLAlchemy很好的实例,以及应用的价值所在。
如果利用好缓存的话,ORM的性能表现并没有对应用太大的影响。

安装“easy_install SQLAlchemy”,导入“import sqlalchemy”,
然后“sqlalchemy.__version__ ”便可以查看版本。

先来整理一下概念上的东西,并假定已有了简单的SQL和ORM的基础:
对象Engine,表示一个数据库,用create_engine创建。
对象MetaData表示对数据的操作,提供方法create_all/drop_all在Engine上创建或删除表
对象Table用以表示数据表,构造器可用于构造表,可在表实例上构建查询并以execute方法执行查询
对象Column表示数据表的列,列的类型定义在sqlalchemy.types。
declarative_base的实例Base是以申明的方式来创建对象,相比用mapper关联表和对象要直观,两者等价所以通常就以派生Base的方式使用了。有属性__table__表示关联的Table对象和属性metadata表示拥有的MetaData对象。
类型Session表示对数据库操作的会话,通过sessionmaker(bind=engine)创建,然后用来创建session实例。实例session有方法add/add_all和delete用于添加删除实例,方法query用以创建Query对象,方法commit和方法rollback用于事务的提交与回滚,方法from_statement直接执行SQL语句。
对象Query用于构建查询,在session实例上以所查询对象为参数创建,提供方法filter,order_by以及序列操作进行查询,提供方法all,one,firs以及count获得查询结果,方法join可以实现表的join查询,简单的查询操作便都在此进行。
aliased用于解决多表查询时的命名冲突问题。
外键列ForeignKey用来指向另一个表的主键,用以建立表间关系。数据实例的API通过relationship函数给模型添加属性。

当前手头没有打算写实际的例子,所以依然是试验目的的代码,目的仅是演示一下代码的流程:
#+BEGIN_SRC python
print "create_engine"
from sqlalchemy import create_engine
engine = create_engine('sqlite:///:memory:', echo=True)

print "declare_schema"
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey
Base = declarative_base()
class Entry(Base):
     __tablename__ = 'entry'
     id = Column(Integer, primary_key=True)
     name = Column(String(50))
     category_id = Column(Integer, ForeignKey('category.id'))
     category = relationship("Category", backref="entries")
     def __init__(self,name,category):
        self.name = name
        self.category = category
     def __repr__(self):
        return "<Entry %s>"%self.id
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship, backref
class Category(Base):
     __tablename__ = 'category'
     id = Column(Integer, primary_key=True)
     name = Column(String)
     #entry_id = Column(Integer, ForeignKey('entry.id'))
     def __init__(self,name):
         self.name = name
     def __repr__(self):
         return "<Category %s>"%self.id
Base.metadata.create_all(engine)

print "use_db"
from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)
session = Session()
c = Category("hello_world")
session.add(c)
session.add(Category("hello_earth"))
session.commit()
for i in range(1,10):
     item = Entry(str(i),c)
     session.add(item)
session.commit()
for i in session.query(Entry).filter(Entry.id==5).all():
     print i
     print i.category
print session.query(Category).count()

print "END"
#+END_SRC


* Elixir

Elixir构建在SQLAlchemy之上,提供直观易用的声明式接口。
在使用方式上Elixir类似于SQLAlchemy的declarative扩展,而且在API上有所简化以方便使用,也隐藏了一些映射的实现细节。

安装“easy_install Elixir”,导入“from elixir import *”,之后便可使用。
值得注意的是对于Elixir未包装的功能,SQLAlchemy的模块依然是需要且肯定会使用到的,特别是查询方面的Query对象。

然后直接写一段试验用的代码了:
#+BEGIN_SRC python
from elixir import *

metadata.bind = "sqlite:///:memory:"
metadata.bind.echo = True

class Post(Entity):
     using_options(tablename='post')
     title = Field(Unicode(30))
     content = Field(UnicodeText)
     comments = OneToMany('Comment',order_by='-id')
     def __repr__(self):
        return "<Post %s>"%self.title
class Comment(Entity):
     content = Field(UnicodeText)
     post = ManyToOne('Post')
     def __repr__(self):
        return "<Comment %s>"%(self.content[:5])

#from model import *
setup_all()
create_all()

p = Post(title=u"demo",content=u"This is a test post.")
for i in range(1,10):
     p.comments.append(Comment(content=u"#%s comment"%i))
session.commit()

for i in Post.query.all():
     print i,i.comments
p.delete()
print Post.query.count()

cleanup_all(True)
#+END_SRC


* Camelot

一个基于PyQt和Elixir依照Django的Model和Admin接口用于开发类似access桌面MIS的RAD框架。
内含一个类似[[http://code.google.com/p/formlayout/]]的form窗口生成工具。

这个就纯观赏用。


* Python

语言的解析树本身就是一个静态的数据,不过不会去直接使用它:
#+BEGIN_SRC python
import parser
st = parser.expr('a + 5')
print parser.st2tuple(st)
a = 5
code = st.compile()#same as 'parser.compilest(st)'
import pprint
pprint.pprint(eval(code))
#+END_SRC
较通常的做法呢是实现对象的__getattribute__/__getattr__方法,以及hasattr/getattr这样的方法,或者类似__class__/__dict__获得中对象所关心的属性。
不过通常也只是这样来获取数据,而没有把数据写成py文件的习惯(不然就等同于使用了罪恶的eval函数了)。


* 闲扯

因为目的是对想法的提及,所以依然还是想在行文逻辑上有连续的感觉。
(上面这句就有问题来着...)
在Django中,模板中数据的来源有两个方面,一是由视图传入的字典提供哦,一是在模板渲染时调用所编写的标签或过滤器返回的结果。对于单页面的网页在浏览器中缓存的是最终显示的结构,在访问新的数据时,仅在样式和脚本上有所缓存。
如果需要把未渲染的模板提前保存在本地的话,就需要在渲染时执行对服务器的全程调用,这就是当前Ajax技术所实现的事情。服务器返回的不再是完整的页面,而是html/text片段,或xml/json数据,或js可执行代码。
将本地的页面更多的作为显示的容器,还是将远程的调用仅涉及数据相关的API,是把功能实现在客户端还是服务器的两种倾向。
对于PyQt这样Api较封闭且脚本类的东西来说,利用漂亮的QWidget及其派生类树形结构。可以在本地程序中下载全程的代码,然后无缝地作为本地程序来执行。
Firefox的xul技术就是这样一种程序环境,程序以扩展的性质安装到本地,通过Market来分发,通过签名来保障安全。而且界面和Qt一样是本地的,而不仅作为嵌入而渲染。如今Eclipse,Notepad++都有各自的扩展Market,而IM之类的软件也可做这样的平台。
现在觉得Chrome的Market更好用一点,应为扩展更接近单纯的网页。虽然功能有些限制,不过填删与开发似乎觉得更方便一些。
不过FX的扩展有沙箱like的权限机制,相比在PyQt中去执行远端的代码就是一件危险的事情了(QML的事属于下面一行)。
说到浏览器,HTML5可用来构建富客户端了(代码执行环境放在本机,而数据的储存放在服务器上,代码可以根据需要动态的下载),和即使Debian的软件包可以做到高速的分发以及虚拟机一样快速且底副作用的部署的话,依然是可以感受的其中的区别的。
不过,区别只是出于观察的视角,在给予用户的体验上,之间又有相关联的地方。


闲扯居然也能坑这么久...也没质量什么的...


Sep 27 2010

2010年8月21日星期六

遥远的记忆之海

来试一试麻枝大魔王风格的

----

这里是死后的世界,而年轻的我是记忆之海上的渡船人。
每天都会有亡灵抵达这里,来在这片汹涌的波涛下探寻往日的回忆。
我将用这片小舟载着他们,驶向掩埋于他们意识的幻境之中。

不知道自己是如何开始这项职业的,也不知道是否在遥远的未来会有某个终结点。
大概我便会这么一直以这样让自己感到幸福的方式生活下去吧——每天都可以期待不同的故事。
而自己就做好一个船夫好了,不必因好奇心让记忆为自己增加多余的烦恼。

“坐稳呦~要出发了~”我向船头上来的一位老奶奶说道。
然后熟练而悠闲地推动起身旁的桨,让船缓缓却高速地飞向某个预定的水域。
海水在炫目却冰凉的天空下闪着光芒,一眼望去只能看见浅浅的海面。

不知不觉中,我已经让船平稳地停下而随意地顺着波浪起伏,然后向着船的那一头:
“你的记忆便保存在这里,在这片浑浊的水面之下。不过还有若干事项要知道——
在波浪起来的时候,要努力地往海底望,凭自己的坚定的意志去努力地望。
让自己的身躯仿佛为回忆包围、洗涤、沉浸,直到某一刻海水骤然变得清晰。
这样你便能看见深深的海底,看见那片保存着你的记忆的地方。
那是只属于你的地方,只有凭自己的力量才能看见,而别人的帮助对此无能为力。”

我程序化地说完后,便顾着自己的悠闲在船头躺下了。
看着老者在船的另一头有些吃力地站起身子,然后视线眺望着周围这片广阔的水域。
无边的海面上的这幅小舟飘荡的景象就这样持续的好久,仿佛静止了一般。

老人只是静静的望向远方,一直没有把头低向海面。我出于本职提醒道:
“天黑之前就要返航了,不抓紧时间就无法看到你想寻找的东西了。”
老人似乎没理会我的话,我也无趣再说,看着老人依旧静静地站在船头。

老人最终还是放弃了,没有再去努力尝试,转过身来在船头坐下。
“就真的这样放弃了吗,这可是一个难得的机会。”老人的行为多少让我不解。
“恩”老人轻轻地哼了声,然后坦然地点了点头,颤动着的嘴唇似乎有话要说。

于是我也安下心来,在这水天包裹的空间里躺在船的一角,听着老人传来的言语:
“随着人生漫长的时光,我已经是一个年迈的人了。记忆在一天天流逝,身体也一天天薄弱。
变得不再有力气去触摸身边的世界,对周围事物的意识也越渐模糊。
活着,自己却越来越难以感受到自己是存在着。于是生命的最终一刻,便在完全不知觉中抵达了。

“过完了一生,纵然有坎坷或遗憾,可终究还是幸福的一生,可以让我安心于死后了。
可是消失的记忆依旧是留下的残缺,对于自己最后的时光,竟然不能在心里珍藏着这份幸福。
所以我便来到了这片记忆之海,想弥补这份残缺,想为自己寻找一份安慰。
不过果然我还是有这份幸福感便足够了,这种感受纵然被遗忘,也毕竟曾经存在过。”

我打量着对面的老者,觉得她是在害怕由记忆去触及内心的某些,不禁叹气感到遗憾。
于是等老人平静下略带激动的言语后,我起身撑起船驶上回程之路。
伴着海平面上柔和而巨大的夕阳,身后留下了那片曾经抵达的水域,
也同时告别了那里那些或许将永远被掩埋于海底的对往日的记忆与情感。

这便是一个普通的工作日,随着一天天的经历,我不久也就会把这天淡忘。
因为这毕竟只是我不断往复着的类似的日子,一个没有源头也没有终点的记忆所背负的时光。
接下来一天的客户是一位中年男子,身体健壮没有疾病的样子。
他是意外而死去的吧,我简单地这样判断着。撑起船,又一起踏上一个新的旅途。

船平稳地向预定的某地飞去,今天的海面的波浪依旧缓和,我在船头悠闲地把着方向。
而他在船的另一头显出好奇,往四周那片行船偶然路过的水天之痕,不断地张望。
“这附近不知是埋藏着谁的记忆,在无尽的记忆之海上,恐怕连我也可能不再会抵达这里。”

我静静地坐在船的这一头,以船夫的身份这样说着,然后依旧向着遥远的目光不能触及的地方。
“好了,我是有点兴奋,毕竟是第一次来到记忆之海。如果这样做对航船有影响的话,我会注意的。”
我眼角的余光中能够感受到他的视线,那似乎是一种期待在寻求着言语上的联络。

“我想知道,你为什么要到这里探寻自己的记忆呢?你还没有到一个健忘的年纪。”
“我来寻找记忆,并不是因为它们被我遗忘,而是因为它们已经离我而远去。
我是想寻找一丝安慰——即使将来记忆变得淡去,它们也曾经凭借着自我的存在而存在过。

“这也是那个生者的世界所留给我的痕迹,告诉自己往日的努力的意义所在。
纵然不能在物质上于现在我的建立起沟通,但是分明就是我在这里可以依旧延续下的一种财富。
这也就是我来到记忆之海缘由,想在这里挖掘到的心灵的一份宝藏。”

船速渐渐变缓,即将到达预定的海域,我向他做着说明引着他往船外沿的地方走去。
然后留下他一人等待海风的气息掀起翻滚的浪花,祝福他的思绪将能够触及到脚下海水的彼端。
风与浪下船头的他在海天间映出一个厚实的身影,仿佛是一扇凭空为世界展开的门。

他的声音在一层层的展开着他的过去:“这是我的出生,这是我的父母,这是我的学校,
我的成年,我的工作,我的老婆,我的女儿,我的事业,我的生活……”
海水的蔚蓝色里翻滚着幸福的味道,一丝腥味,又一丝甘甜。

“美好的期待一直在延续着,直到有一天,那天的一场意外,让我只能告别那一切。
回忆是以痛苦终结的,我想把这最后的时刻忘掉,可是又害怕失去了想守护的东西。
我便这样犹豫着在这个死后的世界,后来想法开始有些坦然——

“记忆的存在并不是因为它们被记住,而是它们被经历过。
记忆的痕迹即使随着时光淡去而似乎不曾有过,它们也不会消失依然存在。
不经意间看到自己身体上留下的微小残缺,知道这便是一种以不可弥补的痕迹来保存着记忆。”

“我想我脑海中的记忆也是一样的,是因为我曾今为着他们的存在而做出过什么。
这便足够了,它们就是我在记忆之海下埋藏的一份财富,将永远为海天守候。”
直到回程的航线上,他也一直在向我分享着他的那份满足。

“记忆里的东西无法改变,也不需要改变了,在现在这个世界里我也依然能为自己创造记忆。”
他似乎不在意我这样漫不经心地听着,能否记住他说出的话。
看着周围海面似乎永恒不变的夕阳景色,我想——
他把现在的感受说出来,这样便是让这种感受以与人共同的记忆,让自己能够保留下去。

这也便又是我不知过去与未来的时光里的一天,在永恒的时间轴上的一个渺小痕迹。
接下来一天的客人是位年轻的学生,年龄似乎与我接近,我依然职业地将他引上船。
今天的天空似乎有些异样,不过不会影响到我们的航行,于是又将开始一个新的旅程。

我控着方向,让船往预定的地点飞去,在有些阴沉的天空下划过淡淡的线条。
他和来到记忆之海的大多数人一样,在船的那头打量周围,以未知抚慰着未知。
回忆的味道就是这样的吧,我也顺着他的视线向远方望去,心中划过这样的想法。

如今的船不需要人过多的操作,加之今天格外低的天空,航行显得有些沉闷。
不过如果天空晴朗,这就是个同往日一样的航行了,充满生活的平淡的温馨。
我沉浸在自己的思绪中,忽然感到船身一个摇晃,我看见船对面的那个身影站起身子。

然后他向我走来,我赶忙先稳固下船的航行,听见他说话的声音:
“您比我大吧,我可以叫你哥哥吗?”“我已经不在意自己的年龄了。”
“那我就叫你哥哥吧!”“这随便你。”“嗯,哥哥好!”“……”

他在我一旁坐下,没有把刚才的热情继续下去,视线转向远方,有些顾虑的样子。
“来说你为什么要来到这里,是想寻找到怎样的记忆吧。”我以平静的语气想挑起对话。
“不知道,”他把头向着我,带着年轻人的活力,“其实我确实不知道,我没有想寻找的东西。”

“这里有记忆的宝藏,会有让人感到幸福的东西,不要为让自己可以努力的机会而犹豫哦。”
“可是那些记忆是我主动抛开的,它们让我感到难受,我却又为一种缺失感来到这里。”
“好了,既然来到这里了,即使意外,也会找到些东西吧。
它们都曾今属于你,今后也依然是。想着抛弃,就可能会让自己失去更多的东西。”

他看着我的眼睛,像在寻找一种希望。而航行也在这不知觉中驶近了目的地。
“去吧,相信自己心中小小的愿望,在海浪之间寻找能够寻找到的东西。”
“恩,谢谢哥哥。”他答应了,然后走向船的那头。站立在船的边沿上,在阴沉的海天间显得波澜壮阔。

我在船的这头放松下身子,视线漫无目的地望向天空,意识到居然第一次对客人说了自己的想法。
我这是说给谁听的呢。一个陌生人?一个在这死后世界里再也不会相遇的人?还是说……我自己?
我在怀疑着一件事情的意义,是否就代表着自己会做出一些多余的事情呢。

此刻我感到我的思绪与情感被他联系着,从未有过的被一个人所联系着。
想着能为别人的困难添上自己的帮助,同时也敞开心扉地能够去接受别人的帮助。
此刻他转过头来看着我,脸上露出一个微笑,我看着也在自己脸上也露出一个微笑。
这个微笑,一个是第一次在这死后世界里自己希望永远记住的东西。

忽然,一个浪打在了船身上,他侧转的身子没站稳,向记忆之海里跌落下去。
瞬间海风也变得巨大了,阴沉的天空似乎要发展往一个糟糕的方向。
此刻顾不上太多,我得救他。跑向他所在的船头,往波浪翻滚的海水里跳了下去。

我在下沉,身边被黑暗包围着。那是一片无尽的黑暗,我感到瞬间失去了重力,努力划着水却寻找不到方向。
周围也异样的安静,这是一个海面下无声的世界,时间的流淌似乎漫长,甚至凝结。
渐渐地我的心也平静下来,我能感受到自己的呼吸,呼吸在这个海底并无异样。
恐惧之后这我才意识到,这是在一个死后的世界,一个已经不会再死去而离开的世界,
一个欣慰却又孤独的世界,一个没有失去也没有保留的世界,一个让记忆永恒封存的世界。

在这个黑暗无声的世界里,我顺着水流漂浮着,让意识随着身体漂浮着。
在记忆的海洋中以心灵去触摸着曾今,以及那些孕育着现在和未来的过去时刻。
我能体会到在深海之下的幽幽的光芒,像一个指引者在前方对我轻轻的召唤。

我凭借着意志向光芒游去,让光点在眼前接近在身边扩散,觉得这样才能够拯救到什么。
这样能够实现自己对他人的一个守候,一个哪怕在这死后世界已经显得多余的举动。
一种哪怕只是出于内心原始的善良的淳朴的冲动,也要去带着落海的少年顺利的回来。

光芒弥散于海底,如同思绪飘荡在无尽的水中接受着温柔而永恒的抚慰,包裹在我的周围。
渐渐地,光点构成的屏障上清晰出一个影像,在向我讲述着什么。
在向我呈现着一个“生”所在的世界,一个在海底无尽的黑暗里“心”所体会的“光”所组成的世界。

“我站在一个空旷的操场上。”少年的声音在耳边的告诉我,然后飘去了。
我想这是少年的记忆传递给我的信息吧。在他的视角上,我可以以第一人称在看到他的双手。
在这个记忆构建的世界里,体会着他的过去。可是,我却感觉不到他的意识,以及他的存在。

身体开始活动起来,由机械变得自然地走动着,向着教学楼那里去寻找人的踪影。
我的意识仅仅是随着身体漂浮着,在这个世界里不能左右什么,也不会有想去左右什么的愿望。
在阳光下曝露的身体,接触着这里的空气与水分,仿佛在由陌生不断变得亲近着。

与空旷的操场相比,教学楼因为人的踪迹,而显出格外不同的氛围。
身体继续往教室里走去,人的气息的存在让它移动着的脚步学会了自信。
同时身体也是在渴望着阳光之外的什么,让自己来到这人群中去寻找着。

身体在经历着成长,可是这样似乎仍缺少着什么,它所见到的人群都在它面前散开了。
当身体带着疑惑去走进下一个教室,带着或许会有改观的期望,依旧得到的是一个失望的结果。
“果然人们是讨厌我的,这个世界里已经不需要我的存在了。”

此刻身体忽然开始传递出它的意识,让我清晰的感受到意识的存在,而随即痕迹又消失得更加完全。
之后,身体便失去了他本身的驱动,光芒远离而去了。交付于我,而我只是依然麻木地站在教室中央。
刚才跑去教室外的人群开始渐渐返回,他们仿佛无视了我的存在,又回到了他们的日常。

我就这样站在教室里,近距离的带着身体逝去的愿望。去接触人群的气息,去沉浸于海洋的梦境之中。
可是,梦境最终将迎来黎明。“刚才真是一个讨厌的人啊,就这么像无视我们的跑近教室来。”
是“讨厌”吗?一句声音之后,周围的声音也纷纷附和起来,成为这个狭小的教室里共通的话题。

我能感受到一种意志,似乎是身体在传递给我的。
让我出于内心的害怕,从这个空间里,从这个人群中向外跑去,跑向一个与此隔绝的世界中。
我在遥远的地方大口的喘着气。驻足后望,那个教室,那片学校已经变成渺小的身影。

这已经足够远了,这已经是喊声不能再被传递到的距离了。我的心开始变得空荡荡的。
这是一种安心吗?我看着自己缓慢起落的脚步,在坚硬的水泥地面没有留下任何的痕迹。
我再去望那片远去的世界,一个希望着阻隔开的,一个永远只保留着不起眼的存在的世界。

我漂浮在这个梦境的世界,和一个已经属于我的身体,因为躲避着什么而肆意游荡着。
只到有一天,也是偶然而特别的一天,我似乎是感到厌倦。
也似乎是出于好奇,决定再去一次那所学校,再次重拾起曾今想寻找的什么。

校门在眼前清晰了,操场在眼前清晰了,教学楼在眼前清晰了,曾今去过的那所教室在眼前清晰了。
自己的心愿,去寻找一个未知到重要的东西的心愿也这路途上变得清晰了。
这或许就是在凭借着海的梦境的经历,来记忆里所未填的空缺吧。

在那间教室的前,我敲了敲门走了进去,心里已经做好了接受各种意外的准备。
而且不仅仅是躲闪着视线,而是在寻找他们的帮助,寻找着一种重拾遗失的启示。
我站在教室门前,努力地睁开眼,去接受着人群的目光。

身体在人群中肆意的走动着,人们并没有做出异样的反应。
身体想在人群中挑起话题,人们的意志中已经淡忘了他的痕迹了。
这我才发现,身体做出决定已经无法改变,关于那个希望消失于此的决定。
它已经不再属于这个世界了,一个它曾想抛开却一直未割舍的世界。

那我又将飘向何方呢,向着一个新的永恒的世界吗?
在意识在这个身体已经在远离我而去的时候,我又似乎不愿回到那片记忆之海上。
因为在我海的梦境中,在这死后的世界里,我是第一次意识到了我所希望寻找的东西。

我的意识回到的我的身体上,我闭着眼漂浮在黑暗的无尽的记忆之海的深处。
随着水流飘向我一个不知为何方的地方,一个永远将背负着记忆的地方。
在漫长的飘荡中,终于,我决定,睁开我的眼睛,去看看周围的世界。
去看看身边的世界,哪怕看到的依旧只是黑暗,去依旧能在世界里发现着什么。

这是一片天空柔和的色彩,西斜的阳光透过玻璃照射在我面前的床铺上。
双手是棉被的触觉,鼻尖是医药水的味道,身边有机器在运转的声响。
这是一个空荡荡的病房,我孤单的一个人躺在中央。身体不能动,只能望着粉白的屋顶。

我感到内心的一阵寂寞与痛苦,因为孤单而在对外界恐惧而害怕着。
害怕着我的记忆我的所知我的经历,会成为自己的一种负担,会让自己面对着未知却只会守护着丝丝迷茫。
我闭上眼,想象自己正被水包围着,想象就在海底,在那片无尽的黑暗中。
希望再次抵达那个世界每天航行在记忆之海上,一直地去陪同着别人寻找着别人在寻找的东西。

在这样的孤单中,我的意识又变得模糊开始轻浮,再向一个遥远的地方飘去。
直到忽然间清脆的敲门声让我回过神来,可是我仍害怕着不敢睁开我的眼睛。
我听出来是同班的同学来了,他们的话语声在翻滚着我的记忆,清晰着我的心跳。

那片无尽的大海中也有属于自己的记忆吗?他们一直埋藏在那里而我却没有在意。
可是现在我的有份小小的心愿,想去保留那份记忆。
那是一份需要继续延续的记忆,还尚未到一个被海水所掩埋的时刻。
让它独自在无尽深海之下,忍受着永恒的没有终结的孤单。

此刻,对于刚才的心头那份抛弃“生”的世界的想法,自己觉得可笑。
活着的自己,才是对那个死后世界无尽时光最好的终结。
况且人们一直在为着“生”而努力呢,让我能够再次的活过来。
来让我能够有机会去真正地体会到自己内心的希望,同时也是他人给予的希望。

“真希望你能够早点醒过来哦,和我们一起去看班级篮球赛。”熟悉的声音在面前传递过来。
我的内心出于一种固执,依然让自己的双眼紧紧闭着,用拒绝去回避心头残余的害怕。
“今天我们先走了,明天放学的时候还会来看你的。”然后是一个轻轻的关门声。

病房里恢复了先前的安静,又只剩下我孤单的一个人在这狭小的房间里。
此刻安静得又如同深海的气息,我心头的另一种害怕又让自己猛地设法睁开自己的眼睛。
可是我发现自己的眼睛没能睁开,只是忽然有泪水的热度在脸颊上流淌开来。

——“只是一个旅途,这几天里我去了一个遥远的地方,现在我已经回来了……”

而旅途,却依然将在一个遥远的地方延续下去。

100821-29

2010年8月8日星期日

基于Django的Web应用设计

先呼吸一口气放松一下,把Django的文档在生成一份离线版本,然后去官方的wiki页逛逛:
http://code.djangoproject.com/wiki/DjangoResources。
本来打算是严肃的继续说明Django的功能的,不过现在又觉得来闲扯一会儿会更好。

之所以这么觉得,原因可以参看这篇《偶对django的rant》
http://www.javaeye.com/topic/361310
这篇文字表达对Django繁琐的url调度,晕人的import,别致的template等方面的不满。

其中的观点可以说是有理有据,确实如实反映了Django的设计,
所以才会流传一种类似于“Django只适合写Django风格的站点”的话,upadate
也有人因此就尝试通过替换路由和模板的方式来使用Django的功能。

个人对Django是刚有涉及的地步,并抱着一直的能黑则黑的态度,来表达不悦。
不过既然来使用Django了,也就有必要来体验一下Django方式的应用设计了。
下文便将从这个角度来尽量不跑题地,来扯一点点轻松的话题。

上面便是无趣的开场。


IDE

原本是在用Emacs写Python代码的,在Emacs里输入文本有一种洋洋洒洒行云流水畅畅快快的感觉,补全工具什么的也是安装好了随叫随到

不成问题。
不过仅以文本的方式来对待一个Django项目则显得视野不够开阔,因此就还是动用了启动更慢Eclipse。
按键选择Emacs绑定,快捷键什么的依旧好使。
安装好WebTools和PyDev,这样便可以建立Django项目了。

Eclipse的补全和重构工具总是让人用起来感觉一直不错。
补全除了用来补全对象的成员和方法,更重要的可以自动补上缺少的import(泪目啊~)。
而重构的话,对于Python代码Inline/ExtractLocalVariable可以让局部变量的存在变得清晰。

此外,Explorer支持了多级目录和py文件内部层次的展开,以及调用manage.py的菜单入口。

有好的工具让后面的事情变得舒心一些,毕竟算是好事吧。


History

Django最初是被开发来用于管理劳伦斯出版集团旗下的一些以新闻内容为主的网站的。
对这句的话的理解可以参考Guido van Rossum曾对Django发表的一段评价:
Guido:我是一个对 django 非常满意的用户,

并在项目中使用了一些 django 。我将 django 称为“第二代” python web 框架, 第一代是指 zope 和 twisted, django 是由两个

在堪萨斯新闻报社工作的小伙开发的,并非是一个很有名气的地方。
Chris:很奇怪,Zope, plone 也是来自报纸网站的
Leo :他们要流程化他们的工作流,这对他们可是很重要的事情。
Guido:也许是这个原因吧,堪萨斯的这家报社希望建立一个给当地人提供信息的本地网站,该网站必须对读者的响应非常及时,必须很

快地发布内容,并不是简单把文章发布到网站上这样谁都可以做的事情,它必须很容易更换整个网站的外观,添加一些新的创意,一些新

功能,增加一些新的应用。例如,发布本地体育赛事新闻,提供关于球队链接和照片等各种感兴趣的信息,他们希望这东西能很快运作。

我想他们做这个有两年了吧,这两个小伙子和一群编辑在一起工作,编辑为他们提供内容。在工作的同时,他们觉得有必要做一个框架,

他们从他们的第一个网站应用中提取了框架。 通过编辑对他们不断提出的对网站修改需求,他们对框架增加更好的灵活性,后来他们决

定说“我们开源吧”,他们的想法得到了报社的支持。
简单的说,Django的设计目的为了实现简化便捷的开发流程,通过

这样来快速地进行Web应用的设计与开发。
而它设计的原则是DRY,这代表Don't Repeat Yourself这一法则。
这意味着相比于Django已提供的功能而言,提供一个支持设计与模块重用的框架,是让Django的价值体现得更为显著的事情。


Model

在创建project和一个app之后,首先来进行的是Models部分设计。

参考一下django-simplecms,一个简单的内容管理系统的模型图:

href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEifEroQXUBXJXK4jkOI44emzQ3Je8qhhdXPMHf_U-AP8eAAzku9WVNUpxXuOWdeaj_1NnHBGmlAqhyphenhyphen-jXrW1tzd27CpWRTem9NAdUwxsPG5W7v94-k61RZozYfv9Pfm-dzI89qqPfJM44g/s1600/cms.png">
src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEifEroQXUBXJXK4jkOI44emzQ3Je8qhhdXPMHf_U-AP8eAAzku9WVNUpxXuOWdeaj_1NnHBGmlAqhyphenhyphen-jXrW1tzd27CpWRTem9NAdUwxsPG5W7v94-k61RZozYfv9Pfm-dzI89qqPfJM44g/s400/cms.png" alt=""

id="BLOGGER_PHOTO_ID_5503062974169040690" border="0" />


我这里只是一个helloworld般的例子"posts":
class Post(models.Model):
....title = models.CharField(max_length=255)
....author = models.ForeignKey(User)
....content = models.TextField()
....pub_date = models.DateTimeField(auto_now_add=True)
....last_update = models.DateTimeField(auto_now=True)
....def __unicode__(self):
........return self.title
....class Meta:
........ordering = ['-pub_date']
这部分代码放在models.py文件中或者建立一个名为model目录。
此外这里依照习惯,引用名使用复数小写,模型名为首字母大写的单数形式。
Django中对变量的命名采用小写字母加下划线分割的风格。

模型设置好后,便可将所创建的应用添加到INSTALLED_APPS中并执行syncdb。
如果设置好Django的admin模块的话,就已经可以正确地来编辑模型的数据了。


more about Model

这部来对Model进行一些额外的说明,阅读时可以暂时跳过。

建立模型需要而模型添加字段,用于表示数据,而Django中也提供了字段用以建立模型之间的关系:
Many-to-one关系,使用ForeignKey字段,
例如从属关系一个投票对多个选项,选项有指向投票的ForeignKey。
Many-to-many关系,使用ManyToManyField,会额外建立一张关系模型(含两个ForeignKey),
例如小组和成员之间,一个小组拥有若干成员,每个成员属于若干小组。
One-to-one关系 类似于unique=True的ForeignKey但为两个模型都添加了成员,
例如用于表示模型的派生关系,每个餐厅都对应一个地点,某些地点对应一个餐厅。

声明完模型的字段有,将可以使用Django已经提供了的属性,例如objects,save()。
也可按需要添加自己的方法供调用,或者使用修饰模式来扩展已有的方法。

Django的Authentication系统中,在django.contrib.auth.models有名为User的模型,用来表示站点的一个用户。
使用系统提供的模型的好处是可以在不同的Apps中有一个一致使用的对象,使得一个用户可以在不同的第三方组件中使用。
User的字段有username等,还有groups和user_permissions两个many-to-many字段。
为给用户增加信息,会建立AUTH_PROFILE_MODULE,用一个OneToOne指向User的Profile模型。

ContentType是Django中表示应用和模型的一个模型,并以此提供了GenericForeignKey,可以用来任意模型的某个数据。
一个实例便是Comments模块,用以给任意模型添加评论。
有时Bookmark模型也以这样的机制来实现。


URL

根据上面完成的Models部分,便可对应的进行RESTful URLs的设计。
参照http://microformats.org/wiki/rest/urls,并参照Django的自身模块的设计,来针对模型展开CRUD操作。

例如pages有一个叫Page的简单Model,那么对应的URL可以是:
/pages/ #page_list
/pages/new/ #page_create
/pages/1/ #page_detail
/pages/1/edit #page_upadate
/pages/1/del #page_delete
对有副作用的URL,GET方式为显示编辑框,POST才确认提交。
对于模型中的Relationship,如Comment有ForeignKey指向Page:
/pages/1/comments/ #page_comment_list
/pages/1/comments/new/ #page_comment_create
/pages/1/comments/1/del/ #page_comment_delete
在上面URL中对应模型的名称,使用小写的复数形式。
并以"APP_MODEL_VIEW"的形式(要避免撞车)为每则URL设定一个name以方便调用。
查询条目可以用object_id或者slug,当条目不存在时需要正确的返回404状态码。

通过模型来设计URL,看起来是件很自然称心的事情。

为了使上面的URL可以工作,在项目的urls.py中添加:
(r'^pages/', include('demo.pages.urls')),
然后在

我们的App中建立一个urls.py的副本供编辑。

这里为使用方便,可以上面名为Page的Model中添加:
@models.permalink
def get_absolute_url(self):
....return ('page_detail', [str(self.id)])
另一种更通用与广泛的做法是使用
reverse('page_detail',{"object_id": self.pk})
或者模

板中的
{% url page_detail object_id %}
AND
<a href="{{ object.get_absolute_url }}">{{ object.name
}}</a>
这里要注意的是,Django的URL调度有序列和字典两种形式。

在其他的Web框架中还有一种常见的URL形式:
/{controller}/{action}/{id}
其中controller支持多层的结构

。
所以有时也会使用到
/pages/
/pages/new/
/pages/view/1/
/pages/edit/1/
/pages/del/1/
这样的URL模式。

在用户接触站点的界面中,URL是一个首先会接触到的内容。
这也是为下一步设计站点的显示效果的设计,做一个基础。

通过使用urls.py文件,就像一个.h一样,可以为整个站点提供一个清晰的结构。


View

URL调度的作用是以形式:
url(r'^posts/$', views.index,name="post_index"),
url(r'^posts/(?P\d+)/$', views.detial,name="post_detial"),
为每个URL模式指定对应的View。

View是一个形为:
def detail(request, object_id):
return HttpResponse("You're looking at object %s." % object_id)
的函数,其中的object_id便是URL的正则表达

式中捕获的部分。

依据习惯,我们会将所编写的View从urls.py中取出,放在App的views.py文件或view目录下。
而且在Django已经提供了若干实用的generic views,这就可以根据需要直接使用,或者只做一下简单的修饰。

继续以一个简单的CRUD为例:
info_dict = {
  'queryset': Post.objects.all(),
}
info_dict2 = {
  'post_save_redirect':'/posts/%(id)s',
#通常不设,默认用get_absolute_url的值
  'model':Post,
}
info_dict3 = {
  'post_delete_redirect':'/posts/',
  'model':Post,
}

urlpatterns = patterns('',
url(r'^posts/$',
'django.views.generic.list_detail.object_list',
info_dict,name="post_list"),

url(r'^posts/(?P<object_id>\d+)/$',
'django.views.generic.list_detail.object_detail',
info_dict,name="post_detail"),

url(r'^(?P<topic_id>\d+)/new/$',
'django.views.generic.create_update.create_object',
info_dict2,name="post_create"),

url(r'^(?P<topic_id>\d+)/edit/(?P<object_id>\d+)/$',
'django.views.generic.create_update.update_object',
info_dict2,name="post_update"),

url(r'^(?P<topic_id>\d+)/del/(?P<object_id>\d+)/$',
'django.views.generic.create_update.delete_object',
info_dict3,name="post_delete"),

)
这里的info_dict中可以再放名为extra_context的字典,用于传入额外需要的值或函数。
url也可以简单的只按照顺序使用正则的捕获来向View传入参数。

对于不能直接使用generic views的,可以来自己编写view:
url(r'^(?P\d+)/$',views.list,

name=""),
然后在views,py中添加
def list(request,user_id):
    author = get_object_or_404(User, pk=user_id)
    queryset = author.post_set.all()
    return list_detail.object_list(request,queryset,
paginate_by=3,extra_context={'author':author})
或者就用更加直接的写法:
def index(request):
  latest_post_list = Post.objects.all().order_by('-pub_date')[:5]
  return render_to_response('posts/index.html', {'latest_post_list': latest_post_list},

context_instance=RequestContext(request)))
自行调用模板来渲染出返回的结果。
generic views的代码也是为自行编写View一个很好的参考


Template

从分工的角度说,模板的设计属于设计师的职责。
Django给它的模板系统很大的限制,使得其中蕴含的应用的逻辑依然能够保留到View部分的代码中。
对于上节末的render_to_response代码,模板里可以使用的内容包括模板地址和传入的dict,
还包括RequestContext中的信息,例如变量 user, messages, perms供使用。
也可自行编写tags and filters供{% load **** %}。

所以说呢,设计模板的内容是和对应的View相承接的,直接由Views所传入的内容所决定。
在创建模板文件时会先考虑其了路径和名称,以及其中可以使用的变量或函数。

下面为一组CRUD的GenericView简单的template示例:
这里应用名为posts,有一个叫Post的模型,模板名为默认。

post_list.html
<a href="./new">new</a>
{% if object_list %}
<table>
  {% for post in object_list %}
  <tr>
    <td><a href="{{post.get_absolute_url }}">
{{ post.title }}</a></td>
  </tr>
  {% endfor %}
</table>
{% else %}
<p>no post</p>
{% endif %}
post_detail.html
<div>
  <div>title:{{ object.title }}</div>
  <div>{{ object.pub_data }}</div>
  <div>{{ object.content }}</div>
<a href="../{{ object.id }}/edit/">edit</a>
<a href="../{{ object.id }}/del/">del</a>
<a href="../">back</a>
post_form.html
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
post_confirm_delete.html
title:{{ object.title }}
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Delete" />
</form>
这里只是一个让CRUD可以工作的示例。

通常的模板文件中,会使用到继承结构
{% extends "base_site.html" %}
{% block content %}
...
{% endblock %}
也会再有base.html建立两层关系,为让页面继承栏目的再继承站点的模板。

为显示messages,可在base.html中添加:
{{% if messages %}<ul class="messagelist">
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}</ul>
{% endif %}
以及为user不同的登录状态显示login或logout。

tag 中有linebreaksbr换行转br,也有markup可使用markdown来HTML化文本。


Html & Css

由于Template的继承结构,根据Django的理念在编写View直接使用的模板代码时,
是直接进行网页代码的编写,以分割开显示效果相关以关注其中的逻辑。
所以这一段选择性地说一说站点的前端代码。

xhtml
<h1>标题</h1>
<span>行文本</span>
<strong>加粗<strong>
<em>斜体</em>

<a href="url">链接</a>
<img src="url" alt="text" />图像
<br />换行
<hr />水平线

<p>段落 </p>
<pre>预格式文本</pre>
<div>层</div>

<table border="0">
<tr>
<td>1,1</td>
<td>1,2</td>
</tr>
<tr>
<td>2,1</td>
<td>2,2</td>
</tr>
</table>
表格

<ul>
<li>first</li>
<li>second</li>
</ul>
无序列表
这些代码可以用来组织页面结构。

然后是css
<link rel="stylesheet" type="text/css" href="style.css" /> 引入css文件

strong {font-weight:bold;} 标签选择器
#red {color:red;} id选择器
.center {text-align: center} class选择器

Box
* {
margin: 0;
padding: 0;
}
#box {
border-style: none;
padding: 10px;
}

定位
.box {
float: left;
}
.clear {
clear: both;
}
网页同时使用div的浮动来布局,样式也在css中设定。

可以嵌入JavaScript
<script type="text/javascript"

src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
这里以Jquery为例。

可以使用浏览器中的开发工具来调试站点的各种代码。

然后再考虑一下配色(声音渐低中...


App

根据以上的步骤,以及可以建立一个简单的站点了,比如说一个私人博客。
不过对于有一定复杂度的站点,在设计模型之前,需要为项目进行App的划分。

多App的方式可以使站点的结构变得清晰,也使得App的复用变得方便。

例如:

Pinax是一组用以建立SNS的Apps。
可以创建Pinax项目,或者使用其中的应用,或者阅读其中的代码。
需要注意的是其中Apps之间存在依赖关系。
django-notification就被其中大大多数App可选的使用,
用以向用户发送通知。
django-groups被tribes以及projects使用,
用以建立用户小组。

在Debian的包中,也含了一些Django的Apps
http://packages.debian.org/search?keywords=django
如django-registration django-tagging django-tinymce app-plugins
使用这些组件,除了可以减少重复,以为增加了Apps间相一致的地方。

为了复用方便(Be Portable),需要在编写App时尊崇一些习惯
http://ericholscher.com/projects/reusable-app-docs/

还有一个叫GoFlow的工作流App看起来很有趣。

除了App的复用,也有提供整个项目来进行开发的。
例如Satchmo一个WebShop的App,
django-cms一个CMS的App。

最后说明一下,虽然Django有较好的向后兼容,
但随着版本变化会有不同的内置Apps,所以在第三方代码的实现上也会有不同。
依旧来提官方Wiki,那里始终是个很全的资料收集之处。

这些第三方Apps的存在也是为自己的项目的Apps划分提供一个参照。

像PIL那样的Python库是也会在Django项目中使用的,
更多的可复用apps也可以在Pypi找到若干。

在Django的文件中使用import可以使用Python中通用的相对路径,
也可用以当前应用所在项目名称为开始的绝对路径来引用。


Csrf

这是与安全相关的一个模块,是用于对抗Cross Site Request Forgeries的保护机制。

MIDDLEWARE_CLASSES中添加
'django.middleware.csrf.CsrfResponseMiddleware',
render_to_response时添

加
context_instance=RequestContext(request))
模板中
<form action=""

method="post">{% csrf_token %}
Form依旧按照
def contact(request):
    if request.method == 'POST': # If the form has been

 submitted...
        form = ContactForm(request.POST)

 # A form bound to the POST data
        if form.is_valid

(): # All validation rules pass
            # Process the data in f

orm.cleaned_data
            # ...
            return HttpResponseRedirect('/thanks/')

 # Redirect after POST
    else:
        form = ContactForm() # An unbound form

    return render_to_response('contact.html', {
        'form': form,
    })
另一种form的形式是从ModelForm派生并在Meta中指定model属性。
在构造时指定instance属性,并提供save(commit=True)用于将form的属性赋予模型对象。

其他

django-admin.py inspectdb用于从已有的db生成model.py代码

若干模块可以在不影响应用设计的情况下被添加,例如:
Admin
i18n
Cache
Syndication
以及一些第三方的分页插件机制。


End
练手中...


http://www.indexofire.com/blog/?p=774
http://komunitasweb.com/2010/02/django-tutorial-simple-notes-application/
http://github.com/vicalloy/LBForum

2010年7月22日星期四

web framework - Django

Django

Intro.

http://www.djangoproject.com/
Django框架最初是被开发来用于管理劳伦斯出版集团旗下的一些以新闻内容为主的网站,
其原则是DRY(Don't Repeat Yourself),注重组件的重用性和“可插拔性”以方便敏捷开发。

教程和文档请参考:
http://docs.djangoproject.com/
http://www.djangobook.com/
http://www.ibm.com/developerworks/cn/linux/l-django/
http://blog.ericsk.org/archives/815
下面内容也仅是设法从中整理出若然片段,以供额外的参考。

这里使用的版本是 python 2.6 和 Django 1.2 。


Start

如果不考虑部署仅仅是安装一个开发环境的话,把Django作为一个标准的第三方模块来对待便可以,让"import django"能够成功的载入对应的模块。
执行"django-admin.py startproject mysite"创建一个名为"mysite"的项目目录。
其中有四个文件:
__init__.py 空文件,这里对应"mysite"这个包(可在其他文件中import)
manage.py 命令行工具,提供了若干项目相关的功能
settings.py 该项目的配置文件,可手工编辑
urls.py 用于定义URL的调度,是一个站点接口的组成

下面来在项目中创建一个应用,执行"manage.py startapp polls"。
这样则在项目目录中得到一个名为"polls"的目录,其中有四个文件是:
__init__.py,models.py,tests.py,views.py。
这个包以"import mysite.polls"的方式在项目中使用。

先暂且放开刚才创建的应用回到我们项目的根目录,
通过"manage.py runserver"便可以执行测试服务器。
这样在浏览器中访问"http://127.0.0.1:8000/",
就已经能看到这个Django的应用已经顺利运行了。
并且在测试模式下它会为每次请求重新载入代码,
这样我们就可以随即看到对代码的修改产生的效果。

Django自身的测试代码可以在"tests"目录下中找到。
可作为模块使用的参考,或者复制出来使用。


ORM

首先编辑"settings.py"中"DATABASES"和"INSTALLED_APPS"连接数据库并添加所创建的App,
比如这里的是"sqlite3"和'mysite.polls'。
这个准备工作可以让我们能立即试验接下来要为应用所编写的模型。
执行"manage.py syncdb"如果已安装应用的模型对应的数据库空缺的话,
会执行所需的SQL中的"CREATE"操作。
也提供有其他命令(如"manage.py sql"等)来只生成SQL然后手工执行,
以便于语句本身以及对数据库的调整。

使用对象关系映射模块可以通过声明的方式来使用数据库,
这会为我们自动定义对象的若干方法来方便操作数据并执行对应的SQL语句。
类似的模块,比如ROR中的ActiveRecord实现。


Models

模型声明在app的models.py文件中,例如:
class Category(models.Model):
name = models.CharField(max_length=32)
def __unicode__(self):
return self.name
class Article(models.Model):
title = models.CharField(max_length=64)
published_at = models.DateTimeField('date published')
content = models.TextField()
category = models.ForeignKey(Category)
def __unicode__(self):
return self.title

模型类型派生自django.db.models,并提供若干定义好的Field类型。
对每种类型有若干选项,有的不在调用构造方法时指定也提供了获得默认的值(如null=False,blank,choices)。
在未指定主键的情况下"id = models.AutoField(primary_key=True)"将被自动定义。

模型间的关系用ForeignKey,ManyToManyField以及OneToOneField,
分别用于表示
many-to-one|has_one|belongs_to,
has_and_belongs_to_many 以及
对模型的继承扩展。
虽然ManyToManyField表示的关系是相互的,
也只要在相关联的模型中指定一次。

为使用方便,通常重载模型的__unicode__方法。
还可以通过定义"class Meta"为模型提供定义字段之外的信息,
比如"ordering = ['-published_at']"定义获取对象列表时的默认排序。


QuerySets

在数据库被创建之后,可以执行"manage.py shell"以相互方式使用模型,
首先执行"from mysite.polls.models import Poll, Choice"导入。

除了自己在模型中定义方法,对于CRUD操作Django生成了:

INSERT
from mysite.blog.models import Blog
b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
b.save()
save方法调用才会执行对应的SQL语句。

UPDATE
b.name = 'New name'
b.save()
与INSERT操作会自动区分。
ForeignKey也是使用赋值语句,而ManyToManyField用add等方法。

DELETE
e.delete()
也可对list类型操作。

SELECT操作通过类型的objects成员进行,例如
Entry.objects.all()
这是一个惰性类型,可以无多余性能消耗的Python切片运算。
其他提供的方法有 order_by ,filterexclude, get 以及 count ,可以链式使用。
由于参数是以Python中dict类型传入的,会以文本上"field__lookuptype=value"的形式解析。
例如
Entry.objects.filter(pub_date__lte='2006-01-01')
p = Poll.objects.get(pk=1)
关联类型如果非一个单独的属性(如ForeignKey类似CharField使用),
则提供实例的FOO_set成员(用法同模型的objects)进行操作。


MVC

在Django中称为MTV,
原理略。


URL

URL调度在"mysite/urls.py"中指定,修改其中的"urlpatterns"变量。
Django将根据模式匹配的结果调用对应的函数:
urlpatterns = patterns('',
(r'^polls/$', 'mysite.polls.views.index'),
(r'^polls/(?P\d+)/$', 'mysite.polls.views.detail'),
(r'^polls/(?P\d+)/results/$', 'mysite.polls.views.results'),
(r'^polls/(?P\d+)/vote/$', 'mysite.polls.views.vote'),
)
这里访问"/polls/23/"则会调用"detail(request=, poll_id='23')"。
patterns个第一的参数用于指定通用的前缀,
比如这里若是"mysite.polls.views"下面就可省略只写"index"了。
模式的格式是:
(regular expression, Python callback function [, optional dictionary [, optional name]])

也可以把urls.py复制到每个应用的目录下,
然后以类似"(r'^polls/', include('mysite.polls.urls')"的形式引入原urls.py文件中。
这样能够让URL调度的层次变得清晰。


Views

View函数传入一个HttpRequest并返回一个HttpResponse,例如:
from django.http import HttpResponse
def detail(request, poll_id):
return HttpResponse("You're looking at poll %s." % poll_id)
将提供给匹配"/polls/23/"时调用。

HttpRequest的实例通常命名为request,为View函数的第一个参数
if request.method == 'GET':
do_something()
elif request.method == 'POST':
do_something_else()
通过method获得HTTP方法。
为获得HTTP头,
还提供有GET,POST,REQUEST,COOKIES,FILES,META成员按照字典的方式来使用。

HttpResponse的实例被View函数作为返回值,包括其中的文本以及传送的HTTP头:
response = HttpResponse(my_data, mimetype='application/vnd.ms-excel')
response['Content-Disposition'] = 'attachment; filename=foo.xls'
需要时也可返回它的子类如HttpResponseNotFound的实例,另一种类似的写法是:
from django.http import Http404
def detail(request, poll_id):
try:
p = Poll.objects.get(pk=poll_id)
except Poll.DoesNotExist:
raise Http404
return render_to_response('polls/detail.html', {'poll': p})
通过raise抛出错误而没有用return。

代码执行错误是500错误,它们的默认行为是渲染模板目录下404.html或500.html文件。

HttpResponseRedirect则是302重定向。


Template

Django提供了一套自己的模板语言,它是站点前端和后端的交汇处。
虽然塔和Python并不一致,不过也是以这样的限制区分不同的职责。
它的长相如下:
from django.template import Context, loader
from mysite.polls.models import Poll
from django.http import HttpResponse
def index(request):
latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
t = loader.get_template('polls/index.html')
c = Context({
'latest_poll_list': latest_poll_list,
})
return HttpResponse(t.render(c))

/polls/index.html
{% if latest_poll_list %}
<ul>
{% for poll in latest_poll_list %}
<li><a href="/polls/{{ poll.id }}/">{{ poll.question }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %
使用前,需要先去 settings.py 指定 TEMPLATE_DIRS 的值。

在模板中使用{{ variable }}嵌入其返回值,可以是变量或方法调用。
Filters以类似{{ bio|truncatewords:30 }}的形式使用,
用以处理变量的值,常用的例如default,length,striptags。
Tags以类似{% tag %}...{% endtag %}的形式使用,
用以生成输出的内用,例如for循环,if and else条件处理,block and extends继承和重写。
Comments以类似{# greeting #}的形式使用,用以书写注释或注释掉临时不需要的代码。

特殊符号默认会被自动转义,以增强安全性。载入其他库以{% load comments %}的形式。

模板的渲染则是调用Template的实例的render方法,并以Context的实例为参数。


Shortcut

在django.shortcuts定义了若干混合的MVC来方便使用的函数。
如
from django.shortcuts import render_to_response
def my_view(request):
# View code here...
return render_to_response('myapp/index.html', {"foo": "bar"},
mimetype="application/xhtml+xml")

等价于:

from django.http import HttpResponse
from django.template import Context, loader
def my_view(request):
# View code here...
t = loader.get_template('myapp/template.html')
c = Context({'foo': 'bar'})
return HttpResponse(t.render(c),
mimetype="application/xhtml+xml")
这是render_to_response(template[, dictionary][, context_instance][, mimetype])。

还有若干其他的:
redirect(to[, permanent=False], *args, **kwargs)
表示重定向。

get_object_or_404(klass, *args, **kwargs)
from django.shortcuts import get_object_or_404
def my_view(request):
my_object = get_object_or_404(MyModel, pk=1)

等价于

from django.http import Http404
def my_view(request):
try:
my_object = MyModel.objects.get(pk=1)
except MyModel.DoesNotExist:
raise Http404
类似的是get_list_or_404,使用filter()而不是get()。


Next

关于Django的可重用组件以后贴里再说。

----
Django的风格太自己了,在这点上有些别扭。

2010年7月21日星期三

Web界面的桌面应用程序

标题起空泛了,而且还跑题。。。

qooxdoo

qooxdoo是一个开源AjaxWeb应用程序框架。

其官方说明是
qooxdoo is a comprehensive and innovative framework for creating rich internet applications (RIAs). Leveraging object-oriented JavaScript allows developers to build impressive cross-browser applications. No HTML, CSS nor DOM knowledge is needed.

以个人的熟悉的话,会拿qooxdoo和qt做类比(其实是像Java库)。同样是应用程序框架,不过运行的环境并不相同。
话说qooxdoo可作为swt的前端,那么类比而言做Qt的前端的也是可以的。只不过显示交给了浏览器,而一些功能实现要由Ajax交给服务端(Qt有Network模块)来处理。
说浏览器中的库的话,自然会想到jQuery。而如果仅在Web中呈现GUI的话,jquery-easyui那样从内容展示出发的模块显然会更好用一点。
这么来说2005年便成立项目的qooxdoo,其本意浏览器中实现供JavaScript使用的应用程序开发框架了。


Hello World

使用create-application.py生成一个项目目录,例如
create-application.py --name=demo

在生成的目录中,/source/class/demo/Application.js便是需要编辑的文件。
此外有一个generate.py文件,需要先执行
./generate.py source-all
把全部的Class都导入,这样编写时就可以不必再用它来分析代码的依赖。
之后
./generate.py build
生成发布用的代码,就已经有近500k之巨了。

这里的Application.js已经包含最基本的模板,其中涉及qx的对象机制以及GUI工具箱的使用。
看起来有Java代码的感觉,在sdk中demobrowser有示例代码,playground里可以立即执行代码,"generate.py api"用以为当前项目生成文档,Manual则是首先必须了解到的。

此外,qx提供了Debug和Unit Testing的支持,以及一个Inspector。


Remote Procedure Call

基本的Ajax的话是使用qx.io.remote.Request,高层一点的qx.data.store.Json用于读取json,然后是基于JSON-RPC的qx.io.remote.Rpc。
它们都是按照先创建实例然后设置属性在调用的方式使用。
Rpc提供callSync和callAsync远程调用服务端提供的函数。
对服务端的语言没有限制,调用时需要对数据类型进行转换。

qx第三方提供的RPC_Python模块提供的接口看起还是挺优雅的,也实现了简单session机制。
作为实现的参考还是不错的,可以年久并不能实际使用。
这么来看在RPC方面还是需要来自己实现一点东西,不过这反而可以去使用一些已经熟悉的网络框架吧。
Python自带的话,有个xmlrpc模块。一般的话选个Web开发框架就可以了。

还有一个类似GWT的东西,由Java代码生成qx代码。


Web Interface

一些下载软件有提供本机或远程在浏览器中使用的Web界面。
例如emule,mldonkey,它们将自身作为一个Web服务器。
同过URI或者Form来以GET或POST的方式,使用HTTP协议(CURL也可用)。

如果单纯说HTTP的话,就可以是一个RESTFUL的API了。
不过对于以上所列的两款软件来说,并没有使用Ajax的方式。
只是单纯的信息的展示与提交,不过对这样的应用来说也足够了。

在Python中,webbrowser.open_new(url)会在浏览器中打开地址。
服务端的话派生BaseHTTPRequestHandler并实现do_GET和do_POST方法。
通过wfile回数据时先发head,就像SimpleHTTPRequestHandler实现的那样,
在Py的安装目录下可以看到代码。

不过这显然是以上若干节所说的有区别的,这里偏向于API。
而之前在说的是rich internet applications (RIAs),算Web application的范畴。


pyjs

一个Python Javascript Compiler,由Python代码编译出javascript代码。
属于Pyjamas项目,是一个GWT的Python实现,还包含AJAX framework和Widget Set API。
pyjs包括两部分,一是通过Py的语法树生成js代码,另一个则是用js实现了py的基本对象类型。
有个Pyjamas Desktop子版本用于类似Air的桌面程序。
暂且提及一下,略过。


Code Examples

最后堆点不完整的复制来的代码供参考,来源是各自官方。


qx.helloworld.js
qx.Class.define("demo.Application",
{
extend : qx.application.Standalone,
members :
{
main : function()
{
this.base(arguments);
var button1 = new qx.ui.form.Button("First Button");
var doc = this.getRoot();
doc.add(button1, {left: 100, top: 50});
button1.addListener("execute", function(e) {
alert("Hello World!");
});
}
}
});
或创建窗口:
 var win = new qx.ui.window.Window(
"First Window");

win.setPadding(10);
win.setLayout(new qx.ui.layout.VBox(10));
win.add(new qx.ui.form.Button("Hello World"));

win.open();
继承层次结构是
Object>qx.core.Object>qx.ui.core.LayoutItem>qx.ui.core.Widget>qx.ui.window.Window


qx.io.remote.Rpc
var rpc = new qx.io.remote.Rpc(
"http://localhost:8080/qooxdoo/.qxrpc",
"qooxdoo.test"
);

// synchronous call
try {
var result = rpc.callSync("echo", "Test");
alert("Result of sync call: " + result);
} catch (exc) {
alert("Exception during sync call: " + exc);
}

// asynchronous call
var handler = function(result, exc) {
if (exc == null) {
alert("Result of async call: " + result);
} else {
alert("Exception during async call: " + exc);
}
};
rpc.callAsync(handler, "echo", "Test");

//Aborting a call
//var callref = rpc.callAsync(handler, "echo", "Test");
//rpc.abort(callref);
RPC对象,以及两种调用方法。


RPC with a Python server
#!/usr/bin/python
# -*- coding: ascii -*-

import qxjsonrpc
import qxjsonrpc.http

class ExampleWikiService(object):
def __init__(self):
self.total=0
@qxjsonrpc.public
def add(self, *args):
for value in args:
self.total+=value
return self.total

def main():
server=qxjsonrpc.http.HTTPServer()
server.setService('example.wiki', ExampleWikiService())
server.serve_forever()

if __name__=='__main__': main()
加@qxjsonrpc.session话会传入一个session参数。


Python.BaseHTTPServer
def run(server_class=BaseHTTPServer.HTTPServer,
handler_class=BaseHTTPServer.BaseHTTPRequestHandler):
server_address = ('', 8000)
httpd = server_class(server_address, handler_class)
while True:
httpd.handle_request()
这里需要实现自己的handler_class子类,while语句后面可控制是否退出。


...

标题就是,写了个本地应用,然后拿浏览器渲染界面的意思。。。

2010年7月12日星期一

web framework - web.py

http://webpy.org/
web.py is a web framework for python that is as simple as it is powerful.

web.py写起来简单,所以就写它了,算个引子。
况且这里本就把它当作一个拿来用,而不是拿来学习的东西。
写对性能有要求较高的服务端的话,有tornado和twisted作为选择。
而在Py的Web框架中,相比于DjanGo和TurboGears以及功能更强大的Zope/Plone,web.py则显得比较简单。
web.py资料的量不打,在其官网上有提供。tutorial用于对整体概念的学习,documentation是对模块和接口的描述,cookbook和Code samples是使用代码和代码示例,也提供有若干用于交流的途径。
其自身定位于anti-framework,意思是不去约束用户的使用方式,这样使得web.py用起来更加灵活。不过web.py的可复用的组件不够多,所以说web.py简单也是因为他将复杂度移向了其他的地方。
灵活与单薄可以算是一个权衡web.py的选择依据,也就是不论喜欢或讨厌其风格是都可以有其实用场合。
说网络应用的话php是专门的语言了,如今也有诸多框架可选择,而像drupal那样的cms,也可看作一个功能丰富类型固定的框架了。
web.py中如果再增加一些的约定的话,使用起来代码量会少一点。
以上的简短的介绍,不展开说了。

web.py的模块
当前版本0.34

部署
坑

web.application

官网上已经简明地列出了web.py的基本用法了
import web

urls = (
'/(.*)', 'hello'
)
app = web.application(urls, globals())

class hello:
def GET(self, name):
if not name:
name = 'World'
return 'Hello, ' + name + '!'

if __name__ == "__main__":
app.run()
这段代码中涉及了URL处理,是使用正则表达匹配并捕获字符串并映射到一个命名空间中创建的类型(实现GET或POST方法,并返回要显示的字符串)上。
代码执行后默认来debug模式下,代码的修改会随即生效,也可指定web.config.debug = False。
默认使用http://127.0.0.1:8080/来通过浏览器执行效果的预览,也有提供了API的形式方便调试。
可以修改app.notfound以自定义404错误时所显示内容,创建的app也有Subdir和Subdomain的类型。


web.webapi

这部分是在Web命名空间下提供的一组函数,用于Http协议相关。
在实现GET或POST方法时会用到。

header
web.header('Content-Type', 'text/plain')
web.header("Content-Type","text/html; charset=utf-8")
输出Http头。

input
class hello:
def GET(self):
i = web.input(name = 'web')
return 'Hello, ' + web.websafe(i.name) + '!'
获得GET或POST的输入数据。

setcookie与cookies
web.setcookie('age', i.age, 3600)
参数是名称,值,有效时间。这对函数用于操作cookie。


另一组函数用户返回Http状态,使用raise语句调用。
例如
raise web.seeother("/foo")
是303重定向。
还有若干HTTPError派生的不同类型以表示不同的状态,例如NotFound(message=None)。


web.httpserver

目录static/下用于存放静态内容


web.template

首先是程序部分

render = web.template.render('templates', base='layout')
print render.hello('world')
模板文件放在'templates'目录下,可选参数base表示共用部分。
这里以'world'为参数渲染了'hello.html'文件并返回字符串结果,可以进行其他的处理之后再执行return等来使用。
通过设置globals或builtins属性(dict类型,默认None。),可以提供模板代码可以访问的变量和可以使用的函数。

另一方面则是模板代码的语法了,
它的长相是
$#hello.html
$def with (name)
$var title:"hello"
$name
$用于使用Python语句,例如访问变量(会自动web.websafe)执行函数或者for/while/if及其他语句。
$#layout.html
$def with (page)
$page.title
$:page
这里的title是子模板中定义的。


web.contrib.template

实现了对其他模板引擎的接口。


web.form

该helper模块用于渲染表单和处理验证。
signup = form.Form(
form.Textbox('username'),
form.Password('password'),
form.Password('password_again'),
validators = [form.Validator("Passwords didn't match.", lambda i: i.password == i.password_again)]
)
也可在创建Widget时指定验证条件
form.Textbox("bax", 
form.notnull,
form.regexp('\d+', 'Must be a digit'),
form.Validator('Must be more than 5', lambda x:int(x)>5)),
然后可以在控制器中或者模板中创建拷贝实例再执行.render()方法。

方法.validates()用于让form处理input,之后可以获取输入值
class hello:
def GET(self):
my_form = number_form()
return render.hello(my_form)

def POST(self):
my_form = number_form()
if not my_form.validates():
return render.hello(my_form)
else:
number = my_form['number'].value
if int(number) % 2:
return "Your number %s is odd." % number
else:
return "Your number %s is even." % number

这里的hello.html
$def with (form)
<form name="test" method="POST">
$if not form.valid: <p>Sorry, your input was invalid.</p>
$:form.render()
<input type="submit" value="Check" />
</form>
可以获得表单状态。


web.session

机制session用于保存客户端的状态
if web.config.get('_session') is None:
session = web.session.Session(app, web.session.DiskStore('sessions'), {'count': 0})
web.config._session = session
else:
session = web.config._session
在debug模式执行结果不可靠。
然后使用
class hello:
def GET(self):
session.count += 1
return "You visited " + str(session.count) + " pages."

class bye:
def GET(self):
session.kill()
return ("Bye, web!")

也可把session传递给模板
web.template.Template.globals['session'] = session

web.session的配置在web.config.session_parameters,例如timeout属性。
session可使用DiskStore或者DBStore。


web.db

这是操作数据库的一个接口,有postgresql/mysql/sqlite,需安装Py下的支持模块。
也可用sqlite3,sqlalchemy或一个文件来替换这个模块。
以下代码纯粘贴
db = web.database(dbn='postgres', user='username', pw='password', db='dbname')

entries = db.select('mytable')
db.update('mytable', where="id = 10", value1 = "foo")
db.delete('mytable', where="id=10")
sequence_id = db.insert('mytable', firstname="Bob",lastname="Smith",joindate=web.SQLLiteral("NOW()"))
results = db.query("SELECT COUNT(*) AS total_users FROM users")
print results[0].total_users
results = db.query("SELECT * FROM entries JOIN users WHERE entries.author_id = users.id")
results = db.query("SELECT * FROM users WHERE id=$id", vars={'id':10})
def post(title, body, tags):
t = db.transaction()
try:
post_id = db.insert('post', title=title, body=body)
add_tags(post_id, tags)
except:
t.rollback()
else:
t.commit()
def GET(self):
todos = db.select('todo')
return render.index(todos)
class add:
def POST(self):
i = web.input()
n = db.insert('todo', title=i.title)
raise web.seeother('/')
>>> web.db.sqlwhere({'a': 1, 'b': 2}, grouping=' OR ')
'a = 1 OR b = 2'
select方法可用的参数(传入字符串)有vars,what,where,order,group,limit,offset。
通常在整个应用中把数据库操作包装为model模块。


web.utils

该模块提供若干通用用具,在web命名空间下。
例如:
safestr转换编码
Memoize用于让函数对属于缓存一定时间的返回值
datestr将datetime对象转换为字符串
sendmail发送邮件的函数,相关配置在web.config中
safemarkdown使用Markdown标记

在这里实现了web.py的基础数据类型和相关函数,其他的在输出中可用的实用过滤器也放在这里。

web.net

这里提供了若干网络相关的组件
validaddr从字符串解析出ip地址和端口
httpdate/parsehttpdate字符串编码形式与datetime对象间的转换
urlquote将字符串转换为URL地址中可用
websafe将文本转换为网页中用于显示的UTF-8 HTML

web.http

Http相关组件,用于产生header或url。


other

坑
这里并没有列出web.py的全部组件,并且内容以后会受到API变动的影响的。
其他还是有一些会用到的模块的:
通过web.ctx可以获得客户端信息,类型是ThreadDict。
Python的内置模块中已经有了json的支持了,做ajax时比xml用起来简单一些。
web.webopenid使用openid。
web.wsgi是WSGI组件。

web.py的使用

demo

一个最简陋的计数器
import web

urls = (
'/index.htm','index',
'/.*', 'toidx'
)
web.config.debug = False
app = web.application(urls, globals())

counter=0

class index:
def GET(self):
web.header('Content-Type', 'text/html')
global counter
counter += 1
return str(counter)

class toidx:
def GET(self):
raise web.SeeOther("/index.htm")

if __name__ == "__main__":
app.run()
这个算个引子,表示下面的内容是一些示例来着:

Guestbook,User authentication,Wiki,Blog,Test。

代码就不贴了(坑),也是想变成模块的话复用会更方便一点。
所以去用一些复用程度更高的框架的话,就可以把事情完成的更好更简单一点。


第三方组件
sqlalchemy
模板引擎
CherryPy

然后,这篇就这么简单一些吧。。。