Karrigell 快速体验文集

karrigell+storm web快速开发

::-- ZoomQuiet [2007-12-17 00:41:37]

CPUG联盟::

CPUG::门户plone

BPUG

SPUG

ZPUG

SpreadPython Python宣传

1. 装在推车里的暴风雪

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

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

1.1. 先是一点背景:

1.1.1. Karrigell:

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

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

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

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

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

1.1.2. 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从一开始就为着同时支持低端小程序和高端(多个数据库,十亿级数据量)而设计。

1.1.3. 相关学习资料

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

1.2. 对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那样的形式主义美。

下面是,我的一个小项目的

1.2.1. 代码安排:

在 karrigell webapps 目录下,用你喜欢的名字命名你的karrigell工程。 工程目录下,新建:

conf/  control/   model/  service/  test/  util/  web/  这几个目录,
以及 index.pih   和 __init__.py

我的习惯是把目录恒量,和数据库配置放在 conf 下。

我的解决方法是,把所有自己用到的异常的自定义父类 添加到 core/k_script.py 154行左右 直接抛出捕获异常的 except 字句参数里。 然后再把所有用到的异常引入 modules/mod_ks.py 中。

1.2.2. 集成Storm:

karrigell 集成storm可以说是非常方便。 把storm 解压后,放到 karrigell的 /databases 目录。 在我们的项目的 conf/ 目录下 建立个storm_conf.py 的module 内容如下:

   1 from databases.storm.locals import *
   2 db_url = "postgres://lvs:car@localhost/digyn_dev"
   3 
   4 database = None
   5 store = None
   6 def getStore():
   7     global store
   8     if store == None:
   9         store = Store(getDatabase())
  10         
  11     return store
  12 
  13         
  14 def getDatabase():
  15     global database
  16     if database == None:
  17         database = create_database(db_url)
  18     return database
  19 
  20 #因为采用了module 全局变量。引用此module在最好统一为绝对包名引用

然后,我们就可以在service里 像这样自然的进行数据的持久化操作:

   1 from webapps.digyn.model.orm_models import *
   2 from webapps.digyn.util.pager import Pager
   3 from webapps.digyn.conf import storm_conf
   4 store = storm_conf.getStore()
   5 def add(moduleId,title,bugInfo,findDate,findUserId):
   6     bug = Bug(moduleId,title,findDate,findUserId,bugInfo)
   7     bug.bug_state = constantValue.bugState_new
   8     store.add(bug)
   9     store.commit()
  10     return bug.id
  11 
  12 def get(bugId):
  13     return store.get(Bug,bugId)
  14 
  15 def getBugsPagerForModule(moduleId,bugState,pageNumber=1,pageSize=10):
  16     """获取特定模块下特定状态的bug"""
  17     resList = store.find(Bug,Bug.module_id == moduleId,Bug.bug_state == bugState).order_by(Bug.id)
  18     presList = resList[(pageNumber-1)*pageSize:pageNumber*pageSize]
  19     return Pager(presList,pageNumber,pageSize,resList.count())

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

1.2.3. 后台到前台的代码片段

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

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

class User(Storm):

}}}

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

Service层代码:userService.py:

   1 import md5
   2 from webapps.digyn.conf import storm_conf
   3 from common.exception import *
   4 from webapps.digyn.model.orm_models import User
   5 from webapps.digyn.util.pager import Pager
   6 
   7 store = storm_conf.getStore()
   8 
   9 def addUser(name,password):
  10     if checkUserNameUsed(name):
  11         raise NameDuplicate, name
  12     user = User(name,password)
  13     store.add(user)
  14     store.commit()
  15    
  16 def checkUserNameUsed(name):
  17     if store.find(User,User.name == name).one():
  18         return True
  19     else:
  20         return False
  21 
  22 def deleteUser(userId):
  23     user = get(userId)
  24     store.remove(user)
  25     store.commit()
  26    
  27 def loginValidate(name,password):
  28     password = md5.new(password).digest()
  29     user = store.find(User,User.name == name,User.password == password).one()
  30     if user:
  31         return user
  32     else:
  33         return False
  34 def get(userId):
  35     return store.get(User,int(userId))
  36 
  37 def getAllUser():
  38     return store.find(User)
  39 
  40 def getManageProjects(userId):
  41    
  42     user = store.get(User,int(userId))
  43     projectList = []
  44     for p in user.manageProjects:
  45         projectList.append(p)
  46     return projectList
  47 
  48 def getjoinProjects(userId):
  49     user = store.get(User,int(userId))
  50     projectList = []
  51     for p in user.joinProjects:
  52         projectList.append(p)
  53     return projectList
  54 def assignAdmin(act,userId):
  55     user = get(userId)
  56     if act == 'add':
  57         user.is_admin = True
  58     elif act == 'remove':
  59         user.is_admin = False
  60     store.commit()
  61    
  62 def getUserPager(pageNumber =1,nameQueryStr = None,pageSize=10):
  63         if (nameQueryStr and len(nameQueryStr) > 0):
  64             resList = store.find(User,User.name.like(u"%"+nameQueryStr+u"%"))
  65         else:
  66             resList = store.find(User)
  67         resList = resList[(pageNumber-1)*pageSize:pageNumber*pageSize]
  68         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 编辑 ]

1.2.4. Control层代码

   1 #-*- coding:utf-8 -*-
   2 from webapps.digyn.service import userService
   3 from common.exception import *
   4 PageSize = 10
   5 
   6 def register(name,password,password_again):
   7     name = unicode(name,'utf-8')
   8     if(password != password_again):
   9         Include("/digyn/web/user/register.pih",flash="两次输入的密码不符")
  10         return
  11     try:
  12         userService.addUser(name,password)
  13     except NameDuplicate,userName:
  14         Include("/digyn/web/user/register.pih",flash="用户 %s 已经存在" %str(userName))
  15         return
  16     Include("/digyn/index.pih",flash="%s 你的帐号已添加,请登录"  % (name.encode('utf-8')))
  17 
  18 def login(name,password):
  19     name = unicode(name,'utf-8')
  20     user = userService.loginValidate(name,password)
  21     if user:
  22         session = Session()
  23         session.userId = user.id
  24         if user.is_admin:
  25             session.is_admin = True
  26         else:
  27             session.is_admin = False
  28         Include("/digyn/web/user/userIndex.pih",user=user)
  29         return
  30     else:
  31         Include("/digyn/web/user/login.pih" ,flash="该用户不存在或密码错误")
  32         
  33 def logout():
  34     Session().close()
  35     raise HTTP_REDIRECTION,"/digyn"
  36 
  37 def userManage(act,pageNumber=1):
  38     pageNumber  = int(pageNumber)
  39     _checkIsAdmin()
  40    
  41     if act == "list":
  42         _getUserPager(pageNumber)
  43 
  44 def _getUserPager(pageNumber ,nameQueryStr = None,pageSize=PageSize):
  45     userPager = userService.getUserPager(int(pageNumber),pageSize=pageSize)
  46     Include("/digyn/web/admin/userList.pih",userPager=userPager)
  47 
  48 def assignAdmin(act,userId):
  49     """设定用户是否为系统管理员"""
  50     _checkIsAdmin()
  51     userId = int(userId)
  52     if act == "add":
  53         userService.assignAdmin("add",userId)
  54     elif act == "remove":
  55         userService.assignAdmin("remove",userId)
  56     else:
  57         print "erroe"
  58         return
  59     print "success"
  60 
  61 def deleteUser(userId):
  62     _checkIsAdmin()
  63     userId = int(userId)
  64     userName = userService.get(userId).name
  65     userService.deleteUser(userId)
  66     userPager = userService.getUserPager(1,pageSize=pageSize)
  67     Include("/digyn/web/admin/userList.pih",userPager=userPager,flash="用户 %s 已经删除" %(userName.encode('utf-8')))
  68 
  69 def _checkUserLogin():
  70     if not hasattr(Session(),'userId'):
  71         Session().close()
  72         Include("/digyn/web/user/login.pih",flash="请先登录")
  73         raise SCRIPT_END
  74 def _checkIsAdmin():
  75     if(( not hasattr(Session(),'is_admin')) or (not Session().is_admin)):
  76         Session().close()
  77         Include("/digyn/index.pih",flash="你未被授权访问")
  78         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 编辑 ]

1.2.5. 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?userId=<%=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:

   1 import math
   2 class Pager(object):
   3     def __init__(self,nowList,currentPN,pageSize,totleElementNumber):
   4         self.nowList = nowList
   5         self.currentPN = currentPN
   6         self.pageSize = pageSize
   7         self.totleElementNumber = totleElementNumber
   8     
   9     def getTotlePageNum(self):
  10         return int(math.ceil(self.totleElementNumber / float(self.pageSize)))
  11     totlePageNum = property(fget=getTotlePageNum,doc="return totle page number")
  12     def havePrev(self):
  13         if self.currentPN >1:
  14             return True
  15         else:
  16             return False
  17     def haveNext(self):
  18         if ((self.currentPN*self.pageSize)<self.totleElementNumber):
  19             return True
  20         else:
  21             return False

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

2. 反馈

KarrigellStorm (last edited 2009-12-25 07:18:05 by localhost)

Loading