Flask / MongoDB 搭建简易图片服务器
前期准备通过 pip 或 easy_install 安装了 pymongo 之后, 就能通过 Python 调教 mongodb 了. 接着安装个 flask 用来当 web 服务器. 当然 mongo 也是得安装的. 对于 Ubuntu 用户, 特别是使用 Server 12.04 的同学, 安装最新版要略费些周折, 具体说是 sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list sudo apt-get update sudo apt-get install mongodb-10gen
如果你跟我一样觉得让通过上传文件名的后缀判别用户上传的什么文件完全是捏着山药当小黄瓜一样欺骗自己, 那么最好还准备个 Pillow 库 pip install Pillow
或 (更适合 Windows 用户) easy_install Pillow
正片Flask 文件上传 Flask 官网上那个例子居然分了两截让人无从吐槽. 这里先弄个最简单的, 无论什么文件都先弄上来 import flask
app = flask.Flask(__name__) app.debug = True
@app.route('/upload', methods=['POST']) def upload(): f = flask.request.files['uploaded_file'] print f.read() return flask.redirect('/')
@app.route('/') def index(): return '''
'''
if __name__ == '__main__': app.run(port=7777)
- 注: 在
upload 函数中, 使用 flask.request.files[KEY] 获取上传文件对象, KEY 为页面 form 中 input 的 name 值
因为是在后台输出内容, 所以测试最好拿纯文本文件来测. 保存到 mongodb 如果不那么讲究的话, 最快速基本的存储方案里只需要 import pymongo import bson.binary from cStringIO import StringIO
app = flask.Flask(__name__) app.debug = True db = pymongo.MongoClient('localhost', 27017).test
def save_file(f): content = StringIO(f.read()) db.files.save(dict( content=bson.binary.Binary(content.getvalue()), ))
@app.route('/upload', methods=['POST']) def upload(): f = flask.request.files['uploaded_file'] save_file(f) return flask.redirect('/')
把内容塞进一个 bson.binary.Binary 对象, 再把它扔进 mongodb 就可以了. 现在试试再上传个什么文件, 在 mongo shell 中通过 db.files.find()
就能看到了. 不过 content 这个域几乎肉眼无法分辨出什么东西, 即使是纯文本文件, mongo 也会显示为 Base64 编码. 提供文件访问
Created at 2013-09-07 13:10:57
Permanent Link:
/p/514/
|
Post tags:
Flask
MongoDB
Python
Tutorial
Web Server
|
PLY 构造词法分析工具
PLY 需要安装一份. 可以直接通过 pip 安装 # pip install ply
这东西并非一个扩展的正则表达式工具, 而是一个完备的编译器构造工具, 不过这篇文章只打算讨论其词法分析器构造部分. 基本例子 PLY 很魔法的一点是它使用到了模块内部反射. 也就是说在产生一个词法分析器时, 并不是把词法规则传递给 PLY 的接口, 而是依次将一些指定名字的变量或函数定义在 py 文件中. 下面给出第一个例子, 从文本中抓出十进制数值. import ply.lex
tokens = ( 'NUMBER', )
t_NUMBER = r'\d*[.]?\d+'
def t_error(t): t.lexer.skip(1)
ply.lex.lex() ply.lex.input(''' The Chinese mathematician Zu Chongzhi, around 480 AD, calculated that pi ~= 355/113 (a fraction that goes by the name Milv in Chinese), using Liu Hui's algorithm applied to a 12288-sided polygon. With a correct value for its seven first decimal digits, this value of 3.141592920... remained the most accurate approximation of pi available for the next 800 years.
''')
for token in iter(ply.lex.token, None): print token.type, token.value
输出 NUMBER 480 NUMBER 355 NUMBER 113 NUMBER 12288 NUMBER 3.141592920 NUMBER 800
上面的代码有这么一些要点 - 文件中定义了
tokens 表示可能的词元类型; 在官方例子中, 其中的取值通常以全大写的形式出现 - 定义词元规则
t_NUMBER , 其名字是 token 变量中的成员 'NUMBER' 加上前缀 t_ ; 在构造词法分析器时, PLY 会将以 t_ 开头的所有定义收集起来 - 词元规则
t_NUMBER 的取值是正则表达式, 用来匹配所有的数值 - 定义
t_error 函数, 如果什么奇怪的东西混进来, 这个函数会被调用; 不过现在只是抓取数值, 无视其它符号, 所以实现只是跳过一个字符 (skip 的参数是字符数量) - 调用
ply.lex.lex 构造词法分析器 - 调用
ply.lex.input 喂一些输入进去 - 从
ply.lex.token 获得词法分析的结果
利用分词输出 从这个基本例子迈向一个用于简单表达式计算的词法分析器并不困难. 如果按照 之前一篇文章里所演示的那个功能 (请将该文章中最后一段代码保存在 expr_eval.py 中以供这次使用), 需要至少以下这些词元类型
Created at 2013-08-03 20:18:01
Permanent Link:
/p/513/
|
Post tags:
Compiler Construction
PLY
Python
Tutorial
|
NodeJS liteview Postgres Stekinscript 搭建留言板 [完]
接 上篇前期准备安装软件与库 需要安装 - Postgres (建议 9 或以上版本)
- libpq-dev
npm 安装 Postgres 库 npm install pg
配置数据库与表 参考 这篇文章前半部分配置 Postgres 数据库. 如果已经为 PG 用户 psuser 授权使用 mydb 这个数据库, 现在进入数据库 $ psql -Upsuser -dmydb -W Password for user psuser:
建立数据表用于存放留言 mydb=> CREATE TABLE message ( mydb(> id SERIAL, mydb(> content TEXT, mydb(> post_time TIMESTAMP DEFAULT NOW(), mydb(> PRIMARY KEY(id) mydb(> ); NOTICE: CREATE TABLE will create implicit sequence "message_id_seq" for serial column "message.id" NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "message_pkey" for table "message" CREATE TABLE
显示留言信息更新页面模板 之前的页面模板实在显得太单薄, 下面修改一下准备放入留言数据
Guestbook
#{foreach(#messages)}
#{[post_time]} #{[content]}
#{end}
liteview 文档如是说 #{foreach(#XXX)} 即在 XXX 对象上作循环#{[YYY]} 即每次循环的循环变量的 YYY 属性
因此上面的页面模板需要传入留言对象列表, 每个留言对象有属性 post_time , content . 这命名与数据库表是一致的. 然后改一下 index.stkn, 放假数据进去先看看效果 view: require('liteview').create()
exports.get: (request, response): response.writeHead(200, { 'Content-Type':: 'text/html' }) response.end(view.render('index.html', {messages: [ { post_time: '1970-01-01 00:00:00', content: 'Nostalgia', }, { post_time: '1970-01-01 00:00:00', content: 'Eternal Rite', }, { post_time: '1970-01-01 00:00:00', content: 'Shinto Shrine', }, ]}))
刷下页面, 这些东西就出现了. 从数据库加载内容 巧妇难为无米之炊, 现在首先要做的是向数据库插入一些数据. 还是更刚才建表一样, 进入 psql, 使用下面的语句插入一些水帖 INSERT INTO message (content) VALUES ('Message 0'); INSERT INTO message (content) VALUES ('Message 1'); INSERT INTO message (content) VALUES ('Message 2');
Created at 2012-11-19 23:57:13
Permanent Link:
/p/500/
|
Post tags:
NodeJS
Postgres
Stekin
Tutorial
Web Server
|
NodeJS liteview Postgres Stekinscript 搭建留言板 [零]
前期准备配置软件与库 需要安装 - nodejs 0.8 或以上版本 (之后编译 Postgres 模块需要高版本)
- npm
- 下载编译 Stekinscript (非必需)
$ git clone git://github.com/neuront/stekinscript.git
Ubuntu 用户安装 nodejs 0.8 版本请加入下面的源 # add-apt-repository ppa:chris-lea/node.js
创建一个工作目录, 通过 npm 安装要用到的模块 npm install liteview npm install validator
Stekinscript 快速入门 (非必需) 请参见 https://github.com/neuront/stekinscript/wiki/Language-Specification. 对 Stekinscript 没有兴趣的同学可以无视这一步, 在每节之后生成的 Javascript 代码会奉上, 对于 Stekinscript 代码, 可以认为它是更易阅读的 JS. 开工上手 创建如下内容的 main.stkn 文件, 先启动一个简单的服务器 http: require('http')
port: 8888
http.createServer((request, response): response.writeHead(200, { 'Content-Type':: 'text/html' }) response.end(' Hello, World!') ).listen(port, '127.0.0.1')
console.log('Server running at local host port=' + port)
使用 stekin 编译它 (请用合适的路径替换下面的 stekin, 或者将编译生成的 stekin 可执行程序加入 PATH) $ stekin -i require < main.stkn > main.js
生成的 JS 代码类似 (下面这是经过去除额外括号与重新排版的, 实际上现在 Stekinscript 生成的代码真是惨不忍睹) (function () { const s_http = require("http"); s_http.createServer(function (s_request, s_response) { s_response.writeHead(200, { "Content-Type": "text/html" }); s_response.end("Hello, World!"); }).listen(8888, "127.0.0.1"); console.log("Server running at local host port=8888"); })();
然后使用 node 来运行它, 就在本地搭建了一枚服务器 $ node main.js Server running at local host port=8888
访问 http://localhost:8888/ 就可以看到服务器响应了. Makefile 在目录下加入如下内容的 Makefile, 以便更便捷地生成 JS 文件 (其中的四点 .... 表示一个制表符, Makefile 必须用这货来缩进!)
Created at 2012-11-19 21:31:06
Permanent Link:
/p/499/
|
Post tags:
NodeJS
Stekin
Tutorial
Web Server
|
模板之工 - Mako 上手篇
简介Mako 是一个 Python 模板程序. 用于给定一个字符串, 或给定一个文件, 使用 Python 对象值动态地替换其中的一部分内容. 可是使用 pip 或 easy_install 安装 Mako 库. # pip install Mako # easy_install Mako
使用基本文本替换安装完成后, 在交互环境中可以通过下面的代码来测试 Mako >>> import mako.template >>> print mako.template.Template('Hello, ${ what }!').render(what='Mako') Hello, Mako!
上述代码中, 引入 Mako 模板模块, 并使用模板对字符串 Hello, ${ what }! 中的动态内容进行替换. 动态内容是形如 ${ what } 这样的 Mako 标记, 它表示将传递给 render 函数的字典中, what 对应的项的值替换这个标记. 因此上面的 render 结果便是 Hello, Mako! . 替换文件中的动态内容更多的应用场景是将需要生成的内容写成一个文件模板 (如 HTML 页面), 读取这个页面并生成结果内容. 在当前目录下创建 hello-mako.html 文件, 内容如下
Dear ${ username }, welcome back!
并使用如下的 Python 代码 import mako.template templ = mako.template.Template(filename='hello-mako.html') print templ.render(username='Mako')
就能看到输出的结果 HTML 代码了. 使用中文及指定编码上面的页面模板代码中如果包含中文或其它非 ASCII 字符, 如
你好, ${ username }, 欢迎回来!
那么这一段程序在构造 mako.template.Template 对象时多半会挂掉. 要为 Mako 模板指定输入编码方式才行 import mako.template templ = mako.template.Template(filename='hello-mako.html', input_encoding='utf-8') print templ.render(username=u'柑奈')
分支接下来, 如果这个用户是个管理员用户, 那么希望为这个用户呈现一个后台管理的入口 (当然其它用户就没这个福利了), 那么可以用 Mako 提供的分支来实现 你好, ${ username }, 欢迎回来!
%if is_admin: 管理页面 %endif
并使用如下语句分别测试 templ = mako.template.Template(filename='hello-mako.html', input_encoding='utf-8') print templ.render(username=u'柑奈', is_admin=False) print templ.render(username=u'柑奈', is_admin=True)
%if %endif 之间表示一段分支. 如果确实需要书写一个百分号, 而不是作为 Mako 的指令符号, 那么此处须连续两个百分号 %% 来转义, 通常页面上似乎也不会出现那么多百分号, 所以这个应该还是可以接受的吧. 关于分支, 更详细的例子是 %if condition_a: do_a %elif condition_b: do_b %else: do_c %endif
对象式引用
Created at 2012-07-30 12:37:19
Permanent Link:
/p/491/
|
Post tags:
Mako
Python
Template
Tutorial
|
风之力 - Tornado 搭建基于 WebSocket 的聊天服务
这年头 Python web 框架是有点泛滥了. 下面要介绍的是 facebook 的开源框架 tornado. 这东西比较简单, 而且自带 WebSocket 支持, 可以用它做个简单的聊天室. 读者最好已具备 Javascript 与 WebSocket 的基础知识. 安装 使用 easy_install 能很方便地爬到 tornado. 或者, 下载源代码, 解包后在源码目录执行 $ python setup.py build # python setup.py install
即可. 开张 首先还是来个 hello world. import tornado.web import tornado.ioloop
class Index(tornado.web.RequestHandler): def get(self): self.write(' Hello, world!')
if __name__ == '__main__': app = tornado.web.Application([ ('/', Index), ]) app.listen(8000) tornado.ioloop.IOLoop.instance().start()
保存为 main.py, 然后执行 $ python main.py
并访问 http://localhost:8000/ 即可看到页面中的 "Hello, world!". 在分支中定义的 app 在构造时接受的一个列表参数 [ ('/', Index), ]
用来配置 URL 映射, 比如这里访问根路径则映射至 Index 实例去处理, 在 Index 实例中, 定义的 get 方法将会处理请求. 处理 WebSocket 连接添加请求处理类 接下来就进入 WebSocket 环节. 先修改返回的页面, 让这个页面在加载后连接服务器. class Index(tornado.web.RequestHandler): def get(self): self.write('''
''')
修改这个类后, 然后在控制台中止服务器 (猛击 Ctrl-C), 并重新启动之. 现在, 访问 http://localhost:8000/ 会遇到 404 错误, 因为 WebSocket 请求的 URL "ws://localhost:8000/soc" 还没有映射任何处理器, 因此这里需要再添加一个, 用于处理 WebSocket 请求的类. import tornado.websocket
class SocketHandler(tornado.websocket.WebSocketHandler): def open(self): self.write_message('Welcome to WebSocket')
并为这个类加上 URL 映射 if __name__ == '__main__': app = tornado.web.Application([ ('/', Index), ('/soc', SocketHandler), ]) app.listen(8000) tornado.ioloop.IOLoop.instance().start()
然后重启服务器, 并访问 http://localhost:8000/ 就可以在页面上看到服务器传来的信息了. 使用模板
Created at 2012-04-23 17:58:27
Permanent Link:
/p/489/
|
Post tags:
Python
Tornado
Tutorial
Web Server
|
对象炼金术 - SQLAlchemy 多外键关联同一实体类
继续 上节 SQLAlchemy 外键的探索. 假设现在需求改变了, 需要给每个 Album 加个字段表示作词者, 而作词者本身也应是 Artist 的实例 (比如很多演唱者会自己为歌曲作词编曲等等), 这时 Album 会有超过一个关联到 Artist 的外键. 如果仅仅简单如下处理 class Album(Base): __tablename__ = 'album' album_id = sqla.Column('id', sqla.Integer, primary_key=True) name = sqla.Column('name', sqla.String) artist_id = sqla.Column('artist', sqla.ForeignKey('artist.id')) artist = sqlorm.relationship( Artist, backref=sqlorm.backref('albums', cascade='all,delete-orphan')) lyricist_id = sqla.Column('lyricist', sqla.ForeignKey('artist.id')) lyricist = sqlorm.relationship( Artist, backref=sqlorm.backref('written_albums', cascade='all,delete-orphan'))
然后添加一点数据进去 def save(): artist0 = Artist(name='aki misawa') artist1 = Artist(name='katou emiri') artist2 = Artist(name='yamada hirosi') album0 = Album(name='stella musica', artist=artist0, lyricist=artist0) album1 = Album(name='hoshikage no ama no hara', artist=artist0, lyricist=artist0) album2 = Album(name='jump!', artist=artist1, lyricist=artist2) session = Session() try: session.add(album0) session.add(album1) session.add(album2) session.flush() session.commit() finally: session.close()
if __name__ == '__main__': save()
结果运行立即悲剧, SQLAlchemy 会报下面的错误 sqlalchemy.exc.ArgumentError: Could not determine join condition between parent/child tables on relationship Album.artist. Specify a 'primaryjoin' expression. If 'secondary' is present, 'secondaryjoin' is needed as well.
也许这是 SQLAlchemy 的问题, 明明这么明了的外键关系咋就跪了呢. 好吧, 简单的正确答案是, 手动配置一下外键的 primaryjoin 属性, 如下
Created at 2012-03-14 12:44:02
Permanent Link:
/p/488/
|
Post tags:
ORM
Python
SQLAlchemy
Tutorial
|
对象炼金术 - SQLAlchemy 中关系的级联
在 上一节中谈到了如何把有关联的对象一起塞进数据表中, 现在来试试从数据表里面取出一条数据然后删掉. 在之前代码的基础上添加下面的函数 def remove_album(name): session = Session() try: for a in session.query(Album).filter(Album.name == name).all(): session.delete(a) session.flush() session.commit() finally: session.close()
在这个函数中, 查询 Album 类型, 然后调用查询对象的 filter 函数, 指定列 name 的值严格等于该 name 参数. 接下来, 将查询得到的对象逐个删除. 来看看搞起来如何 if __name__ == '__main__': save() remove_album('stella musica') list_all()
嗯, 看起来还不错的样子. 那么, 接下来试试删 Artist def remove_artist(name): session = Session() try: for a in session.query(Artist).filter(Artist.name == name).all(): session.delete(a) session.flush() session.commit() finally: session.close()
if __name__ == '__main__': save() remove_artist('katou emiri') list_all()
于是乎就悲剧了. 崩盘之前的几行输出似乎是这样子的 = Albums = stella musica + Artist: aki misawa hoshikage no ama no hara + Artist: aki misawa jump! + Artist:
也就是说, remove_artist 确实从数据库中删去了名字为 'katou emiri' 的那个值, 因此名为 'jump!' 的那个 Album 实例的 artist 域成了 None , 悲剧就发生了. 要解决这个问题, 就得在 Album 与 Artist 的关联关系上动动手脚, 加上级联信息.
Created at 2012-03-13 13:44:15
Permanent Link:
/p/487/
|
Post tags:
ORM
Python
SQLAlchemy
Tutorial
|
对象炼金术 - SQLAlchemy 从外键到关系
上节回顾, 使用下面这样的代码保存一个 Artist 与一个 Album , 两条 flush 令人感觉相当不适. def save(): artist = Artist(name='aki misawa') session = Session() try: session.add(artist) session.flush() # 0 album = Album(name='stella musica', artist_id=artist.artist_id) session.add(album) session.flush() # 1 session.commit() finally: session.close()
单纯一个外键还不能很直观地解决对象之间的关联关系. 下面就引入 sqlalchemy.orm.relationship 来改善这块代码. import sqlalchemy as sqla import sqlalchemy.orm as sqlorm from sqlalchemy.ext.declarative import declarative_base as sqla_declarative_base
Base = sqla_declarative_base() # use MEMORY, not a database file. # and disable SQL echo engine = sqla.create_engine('sqlite:///:memory:', echo=False)
class Artist(Base): __tablename__ = 'artist' artist_id = sqla.Column('id', sqla.Integer, primary_key=True) name = sqla.Column('name', sqla.String)
class Album(Base): __tablename__ = 'album' album_id = sqla.Column('id', sqla.Integer, primary_key=True) name = sqla.Column('name', sqla.String) artist_id = sqla.Column('artist', sqla.ForeignKey('artist.id'))
artist = sqlorm.relationship(Artist)
在类 Artist 与 Album 定义完成后, 再用 relationship 为 Album 补上一个类成员, 表示它与 Artist 之间的关系. 这样一来, SQLAlchemy 就能根据此 relationship , 从对象关系中产生外键值, 比如之前的 save 函数可以变成这个样子 Session = sqlorm.scoped_session(sqlorm.sessionmaker(bind=engine))
def save(): artist = Artist(name='aki misawa') album = Album(name='stella musica', artist=artist) session = Session() try: session.add(album) session.flush() session.commit() finally: session.close()
Created at 2012-03-12 17:31:04
Permanent Link:
/p/486/
|
Post tags:
ORM
Python
SQLAlchemy
Tutorial
|
对象炼金术 - 体验 SQLAlchemy
一直听说 SQLAlchemy 是个神一般的 ORM, 近期终于忍不住打算搞一搞, 还是小有收获的, 写一点出来, 欢迎大家来搞. 本文中的例子都是基于 Python 2.7, SQLAlchemy 0.7 的. 数据库使用 SQLite. 废话少说, 先来一段代码 import sqlalchemy as sqla import sqlalchemy.orm as sqlorm from sqlalchemy.ext.declarative import declarative_base as sqla_declarative_base
Base = sqla_declarative_base() engine = sqla.create_engine('sqlite:///test.db', echo=True)
class Artist(Base): __tablename__ = 'artist' artist_id = sqla.Column('id', sqla.Integer, primary_key=True) name = sqla.Column('name', sqla.String)
Base.metadata.bind = engine Base.metadata.create_all()
Session = sqlorm.scoped_session(sqlorm.sessionmaker(bind=engine))
def save_artist(): artist = Artist(name='aki misawa') session = Session() try: session.add(artist) session.flush() print 'Artist id:', artist.artist_id session.commit() finally: session.close()
if __name__ == '__main__': save_artist()
运行这一段代码将会在当前目录创建 test.db 文件作为数据文件, 同时建立一个 Artist 表, 其中有主键字段 artist_id 跟表示名字的字符串列 name. 然后向这个表中插入一个对象, 并输出这个 artist 的 artist_id 在这一行 Base = sqla_declarative_base()
产生了 SQLAlchemy 中用于构造表的基类. 而接下来 create_engine 调用则绑定数据库以及文件, 并设置回显 SQL. 然后重头来了, 声明类型 Artist class Artist(Base): __tablename__ = 'artist' artist_id = sqla.Column('id', sqla.Integer, primary_key=True) name = sqla.Column('name', sqla.String)
以及类成员 __tablename__ 表示表名, 接着以整数类型声明主键, 以及另一列 name 为字符串类型. 不用 varchar 真心舒畅. 接着两句 Base.metadata.bind = engine Base.metadata.create_all()
则是在数据库中建立表. 如果已经建立 (譬如第二次运行这个例子), 删去这两句似乎也没问题. 虽然没看 SQLAlchemy 的实现, 但是构建 Base 类, 以及调用 create_all 建表有点多此一举的感觉. 我还是比较喜欢 GAE 存储那种直白的风格. 后面的 Session = sqlorm.scoped_session(sqlorm.sessionmaker(bind=engine))
Created at 2012-03-09 22:46:26
Permanent Link:
/p/485/
|
Post tags:
ORM
Python
SQLAlchemy
Tutorial
|
GAE 速成简易博客 - 简化 RequestHandler
上节回顾 - 进阶数据库操作在 index.py 和 single_post.py 中, 请求处理器 Index 跟 SinglePost 的代码重复的部分还是挺多的 class SinglePost(webapp.RequestHandler): def get(self): path = os.path.join(os.path.dirname(__file__), 'templates/single_post.html') posts = db.GqlQuery('SELECT * FROM Post WHERE pid = :1', int(self.request.get('id'))) self.response.out.write(template.render(path, { 'post': posts[0], }))
class Index(webapp.RequestHandler): def get(self): path = os.path.join(os.path.dirname(__file__), 'templates/index.html') posts = db.GqlQuery('SELECT * FROM Post ORDER BY date DESC') self.response.out.write(template.render(path, { 'posts': posts, }))
作为程序员, 应该对这样的重复代码零容忍, 当机立断, 大刀阔斧来改起! 从上面的对比看来, 很明显, 对于日常情况中的请求, 服务器端重要的的响应参数包括 那么很好, 来弄个基类, 新建个 base.py 放进去 from google.appengine.ext import webapp from google.appengine.ext.webapp import template import os
class BaseHandler(webapp.RequestHandler): def put_page(self, templ_file, templ_args): path = os.path.join(os.path.dirname(__file__), templ_file) self.response.out.write(template.render(path, templ_args))
开始用新的 BaseHandler 搞起吧. 先来搞 index.py
Created at 2012-01-23 16:17:09
Permanent Link:
/p/480/
|
Post tags:
Google AppEngine
Python
Tutorial
Web Server
|
GAE 速成简易博客 - 更多数据库操作
上节回顾 - 表单处理与基本的数据库操作现在首页能显示文章列表了, 但是 - 文章的顺序貌似是乱的, 而一般来说, 博客系统会按照发布的时间先后顺序来放置
- 构造一个页面, 看指定的某一篇文章的内容
那么, 现在就开始修改数据库吧. 为文章加上 ID 和日期添加属性修改 model.py class Post(db.Model): pid = db.IntegerProperty() title = db.StringProperty(multiline=False) content = db.TextProperty() date = db.DateTimeProperty(auto_now_add=True)
def put_post(title, content):
其中 pid 表示 post id, 是一篇文章的唯一标识; date 是文章的发布时间, 它被设置为对象被存入数据库时自动设置为当前时间 ( auto_now_add=True ). 在 GAE 存储中, 并没有类似 auto_increment 的设置, 因此 pid 的管理需手动进行. 在数据库中, GAE 也有给每个对象设置一个全局唯一的 id , 可以通过如 post.key().id() 来获取, 但是这样获取的 id 值在发布服务器上没有规律可言, 不具备有序性, 不建议使用. 按 ID 排序查询和自增 ID刚刚为 Post 添加的两个属性中, date 是会自动添加到数据库中的, 但 pid 并不会, 得手动给加上. 想要实现自增 ID 的功能, 一个简单的思路是, 从数据库中取出 pid 最大的那篇, 在它的基础上 +1 赋值给新文章即可. 那么继续修改 model.py def next_post_id(): posts = db.GqlQuery('SELECT * FROM Post ORDER BY pid DESC') return 0 if posts.count() == 0 else posts[0].pid + 1
def put_post(title, content): post = Post() post.pid = next_post_id() post.title = title post.content = content post.put()
这里 GQL 中的 ORDER BY pid DESC 表示按照 pid 排序, 而且是降序排列. 另外, 还得修改 add_post.py 里的 AddPostHandler 不能让它乱来了, 而应该改为调用 put_post class AddPostHandler(webapp.RequestHandler): def post(self): # new_post = model.Post() # new_post.title = self.request.get('title') # new_post.content = self.request.get('content') # new_post.put() model.put_post(self.request.get('title'), self.request.get('content')) self.redirect('/add_post')
Created at 2012-01-20 20:43:58
Permanent Link:
/p/478/
|
Post tags:
Google AppEngine
Python
Tutorial
Web Server
|
GAE 速成简易博客 - 表单处理与数据存取
上节回顾 - 站点基本配置与模板没有数据滋养的首页还是个半残废, 下面开始折腾数据库. 添加文章入口 虽然添加文章这种事情可以直接通过后台暴力搞数据库来做, 但既然要写的是 Web 应用, 那么写个页面提供添加文章的入口也是理所当然的事情. 页面嘛, 肯定得有个 HTML 模板撑着, 来建个文件, templates/add_post.html
Add Post
Title Content
接下来得增加一个 RequestHandler , 新建文件 add_post.py from google.appengine.ext import webapp from google.appengine.ext.webapp import template import os
class AddPostEntry(webapp.RequestHandler): def get(self): path = os.path.join(os.path.dirname(__file__), 'templates/add_post.html') self.response.out.write(template.render(path, dict()))
这个文件看起来与 index.py 差不多, 只不过因为 add_post.html 不需要参数, 因此传入 render 的字典是空字典. 最后在 main.py 中新增一项, 将一个 URL 映射到 AddPostEntry import wsgiref.handlers from google.appengine.ext import webapp
import index import add_post
if __name__ == '__main__': application = webapp.WSGIApplication([ ('/', index.Index), ('/add_post', add_post.AddPostEntry), ], debug=True) wsgiref.handlers.CGIHandler().run(application)
现在访问 http://localhost:8080/add_post/, 就可以看到这个入口页面了. 不过因为表单还没有设置处理者, 因此点破提交按钮就没有反应的. 处理表单 还是得添加一个处理请求的类, 修改 add_post.py, 加入这个类 import model
class AddPostHandler(webapp.RequestHandler): def post(self): new_post = model.Post() new_post.title = self.request.get('title') new_post.content = self.request.get('content') new_post.put() self.redirect('/add_post')
Created at 2012-01-19 12:31:17
Permanent Link:
/p/477/
|
Post tags:
Google AppEngine
Python
Tutorial
Web Server
|
GAE 速成简易博客 - 开张
前期准备目前 GAE 官方推荐的 Python 版本是 2.7 (终于脱离了 2.5 的泥潭啊), 实际上 2.6.x 版本也是没问题的 (写个 CMS 这种简易的设备, 应该还用不到太多高端的语言特性). GAE 当然也是必不可少的. 下载和安装步骤在 Google Code 上都有 详细文档. 如果使用 ArchLinux, 还可以通过 AUR 安装. 这里就不废话了. 找个地方, 建立一个目录, 比如叫做 cms-build, 切到这个目录作为工作目录. 下面开始. 数据结构既然是写博客, 那么最关键的当然要是文章 (post) 了. 先来建立用于定义数据的源文件 model.py from google.appengine.ext import db
class Post(db.Model): title = db.StringProperty(multiline=False) content = db.TextProperty()
每篇文章的基本属性有标题 title , 内容 content 和创建时间 date . 其中标题不允许多行; 而内容使用 db.TextProperty 类型而不是 db.StringProperty (根据 GAE 文档, StringProperty 只能存至多 500 字符). 首页模板 首先, 将首页给弄出来, 建立目录 templates, 并在这个目录下弄一个 index.html 文件, 内容是
My Blog
{% for post in posts %} {{ post.title }} {{ post.content }} {% endfor %}
在 GAE 中, HTML 模板文件使用的是 django 的模板语法. 上面文件中, {% for ... %} 到 {% endfor %} 是对传入模板的参数 posts 的迭代. {{ post.title }} 则是对 post 对象的 title 的引用, 而 {{ post.content }} 则是引用 content . 简而言之 {% xxx %} 是模板中的控制语句, 而 {{ xxx }} 则是引用值. 在 django 官方网站可以看到 详细文档, 鄙博客之前也对 django 有过 简介. 这个首页是简单了点, 不过呢, 先看看样子. 视图源文件 光有 HTML 模板还不行, 至少, Python 源代码才是核心. 现在添加一个 Python 源文件 index.py
Created at 2012-01-19 12:30:27
Permanent Link:
/p/476/
|
Post tags:
Google AppEngine
Python
Template
Tutorial
Web Server
|
表达式求值之优先关系矩阵法
YACC 或者 ANTLR 或者 PLY 之类的东西是很不错啦, 不过当面对一个类似 C(++) 这么变态的需要将语法和符号表勾搭在一起的语言形式时, 还是可以考虑一下使用 Python 纯手工打造. 搞定表达式求值这种现代高级程序设计语言中基础部分, 用 LR(n), LALR 分析之类的当然是可行的, 假定需要支持括号和下面这些运算 | 算符 | 优先级由高到低 | + - (正负号) | ^ (幂运算) | * / | + - (加减号) | 结合方式 | 数值左边 | 右结合 | 左结合 | 左结合 |
先搞个产生式集合 (运算符就不搞什么转义了, | 表示或) Expr := Term +|- Expr := Term Term := Exponential *|/ Term := Exponential Exponential := Factor ^ Exponential := Factor Factor := +|- Factor := Number := ( Expr ) 接着还要再计算 FIRST 集啊什么的, 等到最后写成代码, 秀吉都嫁人了. 所幸这个世界上无痛的解决方案还是挺多的, 比如为符号设定优先级, 使用优先级来指导算符是应该移进还是规约. 这个方法可称为 优先关系矩阵法. 这里仍然会用到 移进 (Shift) 和 规约 (Reduce) 这两个术语, 不过它们的意义跟 LR 分析法里面的有些不同. 在 LR 分析中, 符号指的是词法分析中得到的 Token, 而不是运算符, 也就是说数和运算符, 以及其它任何东西都放在同一个栈中; 而在运用优先关系法的表达式分析过程中, 符号栈包括两个不同的栈, 一个专门用来存运算符 (这个栈称为 算子栈), 另一个存放数或者已解析的表达式 (这个栈称为 表达式栈). 在这里, 移进表示将运算符压入算子栈, 或将数压入表达式栈; 而规约指的是, 根据运算符的元数, 从表达式栈中弹出指定数目的 遇到一个数时无条件移进, 而遇到运算符则要视情况决定移进还是规约, 而所谓的情况就是算符优先级. 比如下面这个表达式 3 + 4 * 5 + 6 首先读入 3 , 移进 表达式栈 3 算子栈 (空)
然后是 + , 算子栈栈顶为空, 没得比, 那好, 先移进 表达式栈 3 算子栈 +
继续, 4 移进, 接着是 * , 比一下, 发现 * 优先级较栈顶的 + (加号, 不是正号) 优先级高, 那肯定得移进 表达式栈 3 4 算子栈 + *
接着移进 5 , 再往后又是一个 + (加号), 这时栈顶的 * 优先级高于它, 所以可以开始规约流程: 从算子栈弹出 * , 它是二元的, 于是再从表达式栈弹出栈顶 (刚刚压入的 5 ) 和次栈顶 ( 4 ), 将这三者组成表达式 4 * 5 , 再压回表达式栈 (下面以一个二叉树的形象描述) 表达式栈 3 * / \ 4 5 算子栈 +
还没完呢, 现在的算子栈顶 + (加号) 与手头上拿着的 + (加号) 平级, 由于加号是左结合的, 所以应当继续规约, 得到 表达式栈 + / \ 3 * / \ 4 5 算子栈 (空)
现在算子栈又空了, 于是 + (最后强调一次, 加号) 压栈, 最后是 6 , 移进之, 结束时, 整个算符栈从头到尾规约一次, 大功告成. 回顾一下刚才的过程, 规则是 - 前面说过的, 数无条件入栈
- 如果算子栈为空, 移进当前算子
- 如果算子栈顶的优先级高于当前算子, 或者两者优先级相等, 但当前符号是左结合的, 那么进行规约, 直到不满足规约条件
- 算子无法规约时则移进
这一些初步地写成代码类似这样
Created at 2011-04-11 22:31:40
Permanent Link:
/p/250/
|
Post tags:
Compiler Construction
Python
Tutorial
|
软件翻译与 locale
看了一篇 会 "吐核" 的终端, 了解到 wget 还有烦人的 "eta(英国中部时间)" 提示, 看来软件翻译这档子事还真是很能产生喜感的. 在 Linux 环境下, 主流的软件翻译方法是通过工具抓取源代码中的需翻译字符串到外部文本文件, 形成一个字典, 翻译之后在运行期加载. 更专业一点就是, 从软件本身要能国际化 (internationalization, 一般简写为 i18n, 因为 i 和 n 之间有 18 个字符), 支持从源代码中提取字符串来翻译; 而翻译这个步骤则成为本地化 (localization, 简称 l10n). 要让软件具备国际化属性, 这必须程序员们亲自努力, 为需翻译字符串附加一些修饰. 如下面这个程序 int main(int argc, char* argv[]) { if (1 == argc || 0 == strcmp("hello", argv[1])) { puts("Hello"); } else { puts("World"); } return 0; }
是很难具备国际化能力的, 因为文本扫描程序无法区分哪些字符串是需要翻译的. 要加上区分信息很简单, 比如 #define TRANSLATE_THIS(x) x
int main(int argc, char* argv[]) { if (1 == argc || 0 == strcmp("hello", argv[1])) { puts(TRANSLATE_THIS("Hello")); } else { puts(TRANSLATE_THIS("World")); } return 0; }
将文件保存为 hello.c, 使用 xgettext 工具提取字符串 $ xgettext hello.c --keyword=TRANSLATE_THIS -o hello.po 那么 xgettext 将提取被宏 TRANSLATE_THIS 所包括的所有字符串到 hello.po 纯文本文件中. 在 po 文件中, 每个被提取的字符串是一个 msgid, 而翻译则是接下来一行中的 msgstr, 现在所有的 msgstr 都空着, 在 po 文件中直接写入翻译结果并保存即可. 此外还要指定 po 文件头部有文件编码等信息. 翻译完成后, 在本地创建 locale 相关目录, 再使用 msgfmt 工具编译 po 文件为二进制文件, 并放入 locale 目录中 $ mkdir -p $LANG/LC_MESSAGES && msgfmt hello.po -o $LANG/LC_MESSAGES/hello.mo 这个目录的意义待会儿再解释. 使用纯文本编辑器盯着 po 文件翻译条目是很累很无趣的事情, 好在有许多工具可以选取. 比较流行的有 poEdit 和 Qt Linguist. 接下来继续讨论程序的事情. 到此为止, 程序本身还不具备本地化的能力, 因为宏 TRANSLATE_THIS 什么也没干, 程序本身的逻辑并没有变化, 也不知道如何导入翻译目标字符串替换原有字符串. 现在将宏定义换掉, 使用 locale 中的组件来完成本地化工作
Created at 2011-03-20 00:06:59
Permanent Link:
/p/208/
|
Post tags:
C
i18n
l10n
Locale Programming
Tutorial
|
std::unique_ptr< cookbook>
std::unique_ptr 是 C++11 标准的 STL 中, 用来取代 std::auto_ptr 的指针容器 (如果在代码中使用了 std::auto_ptr , 使用 gcc 4.5 之后的版本加上 -std=c++0x -Wall 参数编译时, gcc 会报出警告 std::auto_ptr 已经过时). 同 std::auto_ptr 一样, std::unique_ptr 的基本功效也能当作简单的自动对象管理指针来使用, 如 #include <memory> /* for std::unique_ptr */ #include
struct echo { echo() { std::cout << "ctor" << std::endl; }
~echo() { std::cout << "dtor" << std::endl; }
echo(echo const&) = delete; };
void test() { std::unique_ptr echo_ptr(new echo); }
int main() { test(); std::cout << "." << std::endl; return 0; }
输出应该如 ctor dtor .
类型 echo 在构造和析构时的输出信息可以帮助掌握对象的生命周期. 如上述程序中, echo 对象在 test 函数结束时析构. std::unique_ptr 的复制构造函数被 delete 处理了, 所以类似下面的代码会出错 std::unique_ptr a(new int(0)); std::unique_ptr b(a); // ERROR // deleted function // ‘std::unique_ptr<...>::unique_ptr(std::unique_ptr<...> const&)'
如果确实需要复制一份整数的值, 需要可以做如下操作 std::unique_ptr b(new int(*a));
而如果是想要将 a 所持有的指针转移到 b 中, 需要显式调用 std::move 将来移交指针控制权 std::unique_ptr a(new int(0)); std::unique_ptr b(std::move(a));
此时 a 中的指针会变为 NULL . 明确的转移语义由 std::move 函数表达, 与 std::auto_ptr 暧昧的构造函数不同, std::move 明确指出 std::unique_ptr 对象是通过转移构造进行参数传递的. 当函数需要使用 std::unique_ptr 作为参数或返回值时, 可以是如下几种形式
Created at 2011-03-13 17:34:22
Permanent Link:
/p/181/
|
Post tags:
C++
C++11
Move Semantic
Tutorial
unique_ptr
|
django 架设网站入门指南[贰]
上节回顾 – ORM 和数据库创建 admin 应用 打开 guestbook/urls.py 仔细研究一下注释, 会看到有几行写着, 取消注释来激活 admin. django 的 admin 应用是一个非常不错的后台. 现在就来开启它吧 from django.conf.urls.defaults import *
# Uncomment the next two lines to enable the admin: from django.contrib import admin admin.autodiscover()
urlpatterns = patterns('', # Example: # (r'^guestbook/', include('guestbook.foo.urls')),
# Uncomment the admin/doc line below and add 'django.contrib.admindocs' # to INSTALLED_APPS to enable admin documentation: # (r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin: (r'^admin/', include(admin.site.urls)),
(r'^$', 'guestbook.home.views.display'), (r'^echo/$', 'guestbook.home.views.echo'), (r'^save/', 'guestbook.home.views.save_message'), )
因为是应用, 所以呢, 还得去 settings.py 登记 INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.admin', 'guestbook.home', )
最后要记得执行数据库同步来创建 admin 相关的表 $ guestbook/manage.py syncdb 现在转到 http://localhost:8000/admin/ 会看到登录提示页面啦. 现在填入第一次 syncdb 时输入的帐号密码就能登入了. 不过现在的 admin 还很挫, 甚至不能管理留言. 进入 guestbook/home 目录, 创建 admin.py 源文件, 加入如下内容 from guestbook.home.models import Message from django.contrib import admin
admin.site.register(Message)
刷新页面, 唉, 没有反应, 因为服务器仅监视那些改动过的文件并重新载入, 这次是单独添加的文件所以没有自动重置, 所以得 重启服务器才行. 然后, 就能在 admin 应用中看到留言对象并修改它们了. 定制 admin
Created at 2010-02-04 19:14:52
Permanent Link:
/p/77/
|
Post tags:
django
Python
Tutorial
Web Server
|
django 架设网站入门指南[壹]
上节回顾 – 配置 基本视图和逻辑数据库配置 仍然在 settings.py 中, 找到 DATABASE_ 开头的项目. 现在用 sqlite3 作为数据库, 它已经集成在 python 2.5 以后的版本中, 这样就省去了安装配置的环节. 现在修改这些项目 DATABASE_ENGINE = 'django.db.backends.sqlite3' DATABASE_NAME = 'guestbook/db_file'
这里 DATABASE_NAME 同样需要给出文件的绝对路径, 原理跟之前模板目录那个一样, 如果不能确定每次的工作目录, 那么就填绝对 db_file 的路径吧. 保存设置, 然后执行数据库同步操作 $ guestbook/manage.py syncdb 会输出提示 Creating table auth_permission Creating table auth_group Creating table auth_user Creating table auth_message Creating table django_content_type Creating table django_session Creating table django_site
You just installed Django's auth system, which means you don't have any superusers defined. Would you like to create one now? (yes/no):
这些是 django 为内置的用户, 会话等类型建立的表, 最后询问是否立即建立一个超级用户. 这里答 yes (仅一个 y 是不行的唉), 然后输入用户名, 电子邮箱和密码. 最后会建立表的索引, 然后结束. 现在, 你可以在 guestbook 目录下看到文件 db_file 了, 它是数据库使用的文件. ORM 和数据库操作 django 的 ORM 非常轻松. 打开 home 目录下的 models.py, 建立留言类型 from django.db import models
class Message(models.Model): name = models.CharField(max_length=32) content = models.CharField(max_length=32) dt = models.DateTimeField(auto_now=True)
注, 使用 django 的 ORM, 该类型需要继承于 models.Model , 而要往数据库中放的数据项, 需要是 models 模块中的对应的域类型. 至于 auto_now=True , 它指出时间和日期在该对象插入数据库时自动使用当前日期时间. 类型弄好了, 得再同步一次数据库. 这些操作可以不重启服务器 $ guestbook/manage.py sql home $ guestbook/manage.py syncdb
回到 guestbook/home/views.py, 增加函数 save_message , 让它将表单数据存入数据库, 并重定向回到首页
Created at 2010-02-03 23:09:53
Permanent Link:
/p/76/
|
Post tags:
django
ORM
Python
Tutorial
Web Server
|
django 架设网站入门指南[零]
安装 根据 django 官方文档, 需要 python 2.3 或更高版本. 不过按现在的 python 普及程度, 想必大家的机器上都有 python 2.5 或 2.6 了. 我的操作系统是 ubuntu 9.10, 自带的是 2.6 版本. debian 用户安装 django 很简单, 只需要 # apt-get install python-django 就可以了, 其它发行版看有没有类似的方法. 或者在 官方站点下载源代码安装 祝好运. 建立项目 startproject 首先找到一个地方, 建立一个项目 guestbook, 我们将在这个项目中搭建一个留言板. $ django-admin startproject guestbook 有些系统中 django-admin 并非一个命令, 需要 $ python django-admin.py startproject guestbook 执行完这个之后, 当前目录下会创建一个名为 guestbook 的目录, 里面包含了一个最简单的服务器: 显示一个主页. 执行 $ python guestbook/manage.py runserver 启动后会看到类似以下输出 Validating models... 0 errors found
Django version 1.1.1, using settings 'guestbook.settings' Development server is running at http://127.0.0.1:8000/ Quit the server with CONTROL-C.
这时, manage.py 已经在本地 8000 端口创建了一个服务器, 赶快打开浏览器去访问吧! 为 guestbook/manage.py 加上可执行属性 (在 ubuntu 9.10 下该属性已经自动添加了) $ chmod u+x guestbook/manage.py 创建应用 startapp 在 django 世界的哲学里, 每个功能称之为一个应用. 创建应用也使用 manage.py 来完成. 比如现在用以下命令创建一个首页应用 $ guestbook/manage.py startapp home 这时 guestbook 目录下会多出一个 home 目录, 里面有这些文件 __init__.py models.py tests.py views.py 比较烦的是, 这时得回去修改设置, 打开 settings.py 查找 INSTALLED_APPS , 在这一坨里面加一行 INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'guestbook.home', )
这样 home 应用才算是有了名分. 好, 现在再来实现功能. 因为现在只是处理页面, 所以只需要在 guestbook/home/views.py 里面修修补补就好了. 打开它, 其实相当于是个空文件, 把这一段代码放进去 from django.http import HttpResponse
def display(req): return HttpResponse(' hello, django!')
我知道你已经不耐烦了, 如果你曾经写 php 的话. 不过现在还有最后一步, 修改路径映射. 打开 guestbook/urls.py, 在里面加一句话
Created at 2010-02-03 14:02:06
Permanent Link:
/p/74/
|
Post tags:
django
Python
Tutorial
Web Server
|
All 20 |