Karrigell+Storm的MVC方式快速开发

> 活在这个到处web n.0 的时代,无论工作还是爱好,出个web小项目,这需求对咱程序员实在是太普遍了。 > 作为python爱好者,你会杂办? > 把django资料找出来花个3个月,熟悉它庞大的结构。独特的ORM,和各种taglib ? > 哈,俺们又不是可怜的ruby用户。 > 你完全可以在python世界里信手拈起最适合你的组件,以最快的速度构建起你自己的web框架。

> 如果你的项目符合以下条件,你或许可以考虑 karrigell + storm

>     * 不需要承载海量用户并发访问 >     * 快速上手的开发过程 >     * 需要高可读性和易于维护的代码 >     * 延续你在java中习惯的mvc思想 >     * 使用人性化的ORM进行db操作 >     * 简单的部署步骤

> == 先是一点背景: ==

> === Karrigell: ===

> Karrigell  是轻量级的web框架。灵活,直观,数据库/ORM/模板引擎 独立。

> Karrigell 很容易上手,花个1小时把tutorial一看,就能用html和python代码拼出个简单的web程序出来。

> Karrigell 支持多种方式混合python代码和html。无论你以前有哪一派的背静,都能在karrigell里延续你的经验和习惯。

> Karrigell 部署简单。有python运行环境,就能让它跑的欢畅。

> Karrigell代码成熟。 5年来Karrigell基本保持着半年一个版本速度在成长。

> === Storm   ===

> Storm 是由Canonical开发的一套 Python ORM库,用在支持着ubunut的Launchpad项目上。

> 以下是Storm的一些亮点(翻译自storm官方网站):

> 干净的轻量级API使得Storm的学习上手过程相对轻松。基于Storm的代码也有着友好的维护性.

> Storm由测试驱动模式开发,任何一行没经过测试的代码都被认为存在bug。

> Storm 中的Model类 不需要特别的构造器,也不需要强制使用专门的基类。

> Storm 整体设计得很好。 (代码中不同的部分有着非常清晰的边界。公共api数量小和意义明确)

> Storm从一开始以同时支持轻量级(SQLite)和重量级(PostgreSQL / MYSQL)而设计。

> Storm代码遵循KISS原则编写,代码简单易读,调试方便。

> Storm从一开始就为着同时支持低端小程序和高端(多个数据库,十亿级数据量)而设计。

> === 相关学习资料 === > karrigell和storm 都很pythonic,看完toturial 基本就能上手. > 想深入了解就直接看代码。代码均干净整洁,注释详尽。看这类项目的码是享受啊:

>  * karrigell的toturial地址:http://karrigell.sourceforge.net/en/front.htm >  * ChumpKlutz 朽木兄对这这份toturial进行了翻,可以到他的blog 查看:http://blog.csdn.net/chumpklutz/

>  * storm的toturial地址:https://storm.canonical.com/Tutorial >   * 目前还没有中文版,俺争取下月完成这份toturial的翻译.

> == 对Karrigell的MVC化规范 == > karrigell 附带的demo是学习karrigell最好的途径。Karrigell的各种用法都在这七八个demo中有精彩的体现。但或许是作者刻意想通过这几个 > demo 体现出karrigell的灵活,phi,hip,ks 混合起来蛮容易把人搞晕。而且附带的几个demo > 代码组织都很松散,往往就是把一堆phi,hip,ks,js,gif放在一起了事。如果之前看过rails或django > 可能会不太适应karrigell demo中的这种凌乱感。

> 哈,别慌,如果愿意,你完全可以把你的项目按照天条似的mvc结构来组织。加上少许规范,karrigell也能秀出rails那样的形式主义美。

> 下面是,我的一个小项目的 > === 代码安排: === > 在 karrigell webapps 目录下,用你喜欢的名字命名你的karrigell工程。 > 工程目录下,新建:{{{ > conf/  control/   model/  service/  test/  util/  web/  这几个目录, > 以及 index.pih   和 __init__.py}}}

> 我的习惯是把`目录恒量`,和数据库配置放在 conf 下。 >  * ORM 对象 放在 model 目录; >  * service目录下放置业务代码; >  * util里放入第三方库; >  * test里写点小测试代码。 >  * 所有前台代码,都放到web目录里,所以web目录下可以再新建几个js/ css/ uploadFiles/  这样的目录。 >   * web目录下,只写和前台展现相关的代码,根据页面的复杂程度,在pih和hip中选择。 >  * contorl目录下,只写页面跳转,为web目录下的pih和hip提供变量 和调用相关业务方法的代码。 ks是最好的选择。 > 同一个对象,不同的web行为。可以写在成一个ks中的多个方法。

>  tips:: ks 中不能直接捕获引入方法抛出的异常,因为异常在 core.k_script  里已经被捕获并做了处理。 > 我的解决方法是,把所有自己用到的异常的自定义父类 添加到 > core/k_script.py 154行左右  直接抛出捕获异常的 except 字句参数里。 > 然后再把所有用到的异常引入 modules/mod_ks.py 中。

> === 集成Storm: === > karrigell 集成storm可以说是非常方便。 把storm 解压后,放到 karrigell的 /databases 目录。 > 在我们的项目的 conf/ 目录下 建立个storm_conf.py 的module 内容如下: > {{{#!python > fromdatabases.storm.locals import * > db_url = "postgres://lvs:car@localhost/digyn_dev"

> database = None > store = None > def getStore(): >     global store >     if store == None: >         store = Store(getDatabase())

>     return store

> def getDatabase(): >     global database >     if database == None: >         database = create_database(db_url) >     return database

> #因为采用了module 全局变量。引用此module在最好统一为绝对包名引用

> }}}

> 然后,我们就可以在service里 像这样自然的进行数据的持久化操作: > {{{#!python > from webapps.digyn.model.orm_models import * > from webapps.digyn.util.pager import Pager > from webapps.digyn.conf import storm_conf > store = storm_conf.getStore() > def add(moduleId,title,bugInfo,findDate,findUserId): >     bug = Bug(moduleId,title,findDate,findUserId,bugInfo) >     bug.bug_state = constantValue.bugState_new >     store.add(bug) >     store.commit() >     return bug.id

> def get(bugId): >     return store.get(Bug,bugId)

> def getBugsPagerForModule(moduleId,bugState,pageNumber=1,pageSize=10): >     """获取特定模块下特定状态的bug""" >     resList = store.find(Bug,Bug.module_id == moduleId,Bug.bug_state > == bugState).order_by(Bug.id) >     presList = resList[(pageNumber-1)*pageSize:pageNumber*pageSize] >     return Pager(presList,pageNumber,pageSize,resList.count())

> }}}

> [ 本帖最后由 lvscar 于 2007-12-17 01:12 编辑 ]

> === 后台到前台的代码片段 ===

> 从后台到前台实现用户管理功能的代码片段:

> storm ORM 对象  `orm_models.py:` > {{{!python > import md5 > from databases.storm.locals import *

> class User(Storm): >     __storm_table__='users' >     id = Int(primary=True) >     password =  RawStr() >     name = Unicode() >     info = Unicode() >     is_admin = Bool() >     manageProjects = ReferenceSet('User.id','Project.manager_id') >     joinProjects = > ReferenceSet(id,'_UserProjectRelation.user_id','_UserProjectRelation.projec­-t_id','Project.id') >     joinModules = > ReferenceSet(id,'_UserModuleRelation.user_id','_UserModuleRelation.module_i­-d','Module.id')

>     def __init__(self,name,password,is_admin=False): >         self.name = name >         self.password = password >         self.is_admin = is_admin

>     def __setattr__(self,name,value): >         if name == 'password': >             #self.__dict__['password'] = value   #设值是通过Storm > 属性类的__set__方法(重载 '='操作符 )实现的。改变instance中的值,变化不会被storm察觉 >             processedPassword = md5.new(value).digest() >             User.password.__set__(self,processedPassword) >         else: >             super(User,self).__setattr__(name,value)

> }}}

> 虽说 Storm orm对象不需要继承特别的父类,但继承Storm类会带来一个方便, > 在建立对象关系时,可以用字符串引用其他类。

> Service层代码:`userService.py:` > {{{#!python > import md5 > from webapps.digyn.conf import storm_conf > from common.exception import * > from webapps.digyn.model.orm_models import User > from webapps.digyn.util.pager import Pager

> store = storm_conf.getStore()

> def addUser(name,password): >     if checkUserNameUsed(name): >         raise NameDuplicate, name >     user = User(name,password) >     store.add(user) >     store.commit()

> def checkUserNameUsed(name): >     if store.find(User,User.name == name).one(): >         return True >     else: >         return False

> def deleteUser(userId): >     user = get(userId) >     store.remove(user) >     store.commit()

> def loginValidate(name,password): >     password = md5.new(password).digest() >     user = store.find(User,User.name == name,User.password == password).one() >     if user: >         return user >     else: >         return False > def get(userId): >     return store.get(User,int(userId))

> def getAllUser(): >     return store.find(User)

> def getManageProjects(userId):

>     user = store.get(User,int(userId)) >     projectList = [] >     for p in user.manageProjects: >         projectList.append(p) >     return projectList

> def getjoinProjects(userId): >     user = store.get(User,int(userId)) >     projectList = [] >     for p in user.joinProjects: >         projectList.append(p) >     return projectList > def assignAdmin(act,userId): >     user = get(userId) >     if act == 'add': >         user.is_admin = True >     elif act == 'remove': >         user.is_admin = False >     store.commit()

> def getUserPager(pageNumber =1,nameQueryStr = None,pageSize=10): >         if (nameQueryStr and len(nameQueryStr) > 0): >             resList = store.find(User,User.name.like(u"%"+nameQueryStr+u"%")) >         else: >             resList = store.find(User) >         resList = resList[(pageNumber-1)*pageSize:pageNumber*pageSize] >         return Pager(resList,pageNumber,pageSize,resList.count())

> }}}

> 顶楼那个tips的意义,就在于可以让Service层中写addUser方法时,我们可以直接抛出一个自定意异常,让control层代码可以写成下面这种格­-式:{{{ >      try: >         userService.addUser(name,password) >     except NameDuplicate,userName: >         Include("/digyn/web/user/register.pih",flash="用户 %s 已经存在" > %str(userName)) >         return >     Include("/digyn/index.pih",flash="%s 你的帐号已添加,请登录"  % (name.encode('utf-8')))}}}

> [ 本帖最后由 lvscar 于 2007-12-17 00:25 编辑 ]

> === Control层代码 === >  * `userControl.ks`: > {{{#!python > #-*- coding:utf-8 -*- > from webapps.digyn.service import userService > from common.exception import * > PageSize = 10

> def register(name,password,password_again): >     name = unicode(name,'utf-8') >     if(password != password_again): >         Include("/digyn/web/user/register.pih",flash="两次输入的密码不符") >         return >     try: >         userService.addUser(name,password) >     except NameDuplicate,userName: >         Include("/digyn/web/user/register.pih",flash="用户 %s 已经存在" > %str(userName)) >         return >     Include("/digyn/index.pih",flash="%s 你的帐号已添加,请登录"  % (name.encode('utf-8')))

> def login(name,password): >     name = unicode(name,'utf-8') >     user = userService.loginValidate(name,password) >     if user: >         session = Session() >         session.userId = user.id >         if user.is_admin: >             session.is_admin = True >         else: >             session.is_admin = False >         Include("/digyn/web/user/userIndex.pih",user=user) >         return >     else: >         Include("/digyn/web/user/login.pih" ,flash="该用户不存在或密码错误")

> def logout(): >     Session().close() >     raise HTTP_REDIRECTION,"/digyn"

> def userManage(act,pageNumber=1): >     pageNumber  = int(pageNumber) >     _checkIsAdmin()

>     if act == "list": >         _getUserPager(pageNumber)

> def _getUserPager(pageNumber ,nameQueryStr = None,pageSize=PageSize): >     userPager = userService.getUserPager(int(pageNumber),pageSize=pageSize) >     Include("/digyn/web/admin/userList.pih",userPager=userPager)

> def assignAdmin(act,userId): >     """设定用户是否为系统管理员""" >     _checkIsAdmin() >     userId = int(userId) >     if act == "add": >         userService.assignAdmin("add",userId) >     elif act == "remove": >         userService.assignAdmin("remove",userId) >     else: >         print "erroe" >         return >     print "success"

> def deleteUser(userId): >     _checkIsAdmin() >     userId = int(userId) >     userName = userService.get(userId).name >     userService.deleteUser(userId) >     userPager = userService.getUserPager(1,pageSize=pageSize) >     Include("/digyn/web/admin/userList.pih",userPager=userPager,flash="用户 > %s 已经删除" %(userName.encode('utf-8')))

> def _checkUserLogin(): >     if not hasattr(Session(),'userId'): >         Session().close() >         Include("/digyn/web/user/login.pih",flash="请先登录") >         raise SCRIPT_END > def _checkIsAdmin(): >     if(( not hasattr(Session(),'is_admin')) or (not Session().is_admin)): >         Session().close() >         Include("/digyn/index.pih",flash="你未被授权访问") >         raise SCRIPT_END

> }}}

> Control层一个方法对应 一个web动作,表单参数名直接用做方法参数。 通过url来决定调用哪个方法 > 例如下面的表单实现用户登录: > {{{ > <form action="/digyn/control/userControl.ks/login" method='post'> > <input name="name">用户名</input><br/> > <input type="password" name="password">密码</input><br/> > <input type="submit" value="登录"> > </form>}}}

> Include  和  raise HTTP_REDIRECTION 这两种实现url转向的方法类似 java servlet编程中的 > sendRedirect 和 forward

> 通过_checkUserLogin /_checkIsAdmin   提高安全性,实现 rails中的 before filter 的效果。

> [ 本帖最后由 lvscar 于 2007-12-17 00:40 编辑 ]

> === View 层代码 ===

> 用户列表界面`userList.pih:`{{{ > <html> > <head> > <title>project digyn</title> > <META http-equiv=Content-Type content="text/html; charset=utf-8"> > <link href="/digyn/web/global.css" media="all" rel="Stylesheet" > type="text/css" /> > <script type="text/javascript" src="/digyn/web/js/prototype.js"></script> > <script type="text/javascript"> > function changeState(checkBoxObj){ > var userId = checkBoxObj.value; > var act = ""; > if (checkBoxObj.checked == true){ >     act = "add";}else{ >     act = "remove"; > }

> new Ajax.Request("/digyn/control/userControl.ks/assignAdmin",{ >                 asynchronous: false, >                 method: 'post', >                 parameters: "act="+act+"&userId="+userId, >                 onFailure: function(request){ >                     alert(request.responseText); >                 }

>             });

> }

> </script>

> </head> > <body> > <% > Include("/digyn/web/banner.frag") > Include("/digyn/web/side.frag.pih") > %> > <div id="main">

> <% > pager = userPager

> %> > <table> > <tr> >     <td>用户名字</td><td>现参与项目</td><td>删除</td><td>授权为管理员</td> > </tr> > <%for user in userPager.nowList: %> >     <tr> >         <td><%=user.name.encode('utf-8')%></td>

>         <td> >         <% for p in  user.joinProjects :%> >         <%=p.name.encode('utf-8')%>&nbsp; >         <% end %> >         </td>

>         <script type="text/javascript"> >         function openUrlWithConfirm(url){ >             if (confirm("确实要删除吗?")){ >                 document.location = url; >             } >         } >         </script> >         <td> >         <% if user.manageProjects.count() >1 :%> >          项目负责中 >         <% end %> >         <% else :%> >         <a href="#" > onclick="openUrlWithConfirm('/digyn/control/userControl.ks/deleteUser?userI­-d=<%=user.id%>')">删除该用户</a> >         <% end %> >         </td> >         <td> >         <input type="checkbox" >         value="<%=user.id%>" >         <% if user.id == Session().userId: >              print "disabled" >         %> >         <% else: >             print "onclick='changeState(this)'" >         %> >         <% if (user.is_admin):%> >         checked >         <% end %>

>         </td> >     </tr> > <%end%> > </table> > <% > print "<p> 共有记录 %s条,分为%s页,每页%s条记录 ,当前第%s页<br/>" % > (pager.totleElementNumber,pager.totlePageNum,pager.pageSize,pager.currentPN­-) > %> > <% if pager.havePrev():%> >       <% print "<a > href='/digyn/control/userControl.ks/userManage?act=list&pageNumber=%s'>上一页<­-/a>" > % (pager.currentPN-1) %> > <%end%> > <% if pager.haveNext():%> >     <% print "<a > href='/digyn/control/userControl.ks/userManage?act=list&pageNumber=%s'>下一页<­-/a>" >  % (pager.currentPN+1) %> > <%end%>

> </div> > </body> > </html>

> }}}

> 分页器:`pager.py:` > {{{#!python > import math > class Pager(object): >     def __init__(self,nowList,currentPN,pageSize,totleElementNumber): >         self.nowList = nowList >         self.currentPN = currentPN >         self.pageSize = pageSize >         self.totleElementNumber = totleElementNumber

>     def getTotlePageNum(self): >         return int(math.ceil(self.totleElementNumber / float(self.pageSize))) >     totlePageNum = property(fget=getTotlePageNum,doc="return totle page number") >     def havePrev(self): >         if self.currentPN >1: >             return True >         else: >             return False >     def haveNext(self): >         if ((self.currentPN*self.pageSize)<self.totleElementNumber): >             return True >         else: >             return False

> }}}

> [ 本帖最后由 lvscar 于 2007-12-17 00:46 编辑 ]

> = 反馈 = > [[PageComment2]]

> -- > '''Time is unimportant, only life important! > 过程改进乃是开始催生可促生靠谱的人的组织! > '''http://zoomquiet.org > 博 @http://blog.zoomquiet.org/pyblosxom/ > 维 @http://wiki.woodpecker.org.cn/moin/ZoomQuiet > 豆 @http://www.douban.com/people/zoomq/ > 看 @http://zoomq.haokanbu.com/ > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ > Pls. usage OOo to replace M$ Office.http://zh.openoffice.org > Pls. usage 7-zip to replace WinRAR/WinZip.  http://7-zip.org > You can get the truely Freedom 4 software.

你可能感兴趣的:(Karrigell+Storm的MVC方式快速开发)