status

正式

ZoomQuiet

完成度:97%

KDay6: 利用 mm 人性化组织成员信息

  • 此mm 非彼 MM 吔 ;-) 乃指 FreeMind 思维图谱文件,默认后缀是.mm因而得名,,

FreeMind的另类用法

继承原先问卷系统的要求:问卷成绩的统计是针对团队的所以,得有方法知道回答者的成员组织信息,以便对应分组统计.

  • 小白第一时间想到了 FreeMind! 因为:

    • 如果使用DB,怎么都得立成员表和组织表,然后还得根据什么ID外键关系什么的进行对应选择什么的,好麻烦的
    • 还是经过行者推荐后,轻松使用起来的 FreeMind 直观,操作也非常快捷--复制,移动太爽直了!

    • 最关键的, FreeMind 文件本身其实就是XML格式的文本! 本质上这也是种数据库,可以直接使用的

  • 行者们曾经提供过freemind.xsl+freemind.mm 可以配合在浏览器中输出了 mm 文件

脚注:: 
XSL模板其实无法直接解析.mm文件,
得手工在头部追加个xml头声明,类似:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type='text/xsl' href='freemind.xsl'?>
以便通知浏览器套用什么XSLT模板

详细:: 
PCS402 XML
介绍了这一风生水起的技术领域,
分享了在Python 中对XML处理思路

详细:: 
PCS403 思維导图
介绍了导图这一种极高效的思维记录和辅助工具;
分享了流行的导图工具,其中包含了对 FreeMind 这一轻盈完备的导图工具的愉快体验!

图 KDay6-9 .xsl解析手工增补后的.mm文件输出时情景

哈哈哈! 由此,小白大致知道了 FreeMind 的数据格式:

  1. 所有节点是统一的 <node>

  2. 属性和值,全部是UTF8编码的字串

所以,只要有简单的约定,就可以作为 Xpath 的过滤参数来理解真正的数据意义了

  • 以上的组织成员信息就简单依从以下小小的约定:
    1. 一级节点都是部门描述,中文
    2. 二级节点是属性描述:
      • dept 说明部门的整体信息
      • staff 汇集成员信息,类似部门的节点组织
      • total 成员总数

图 KDay6-2 FreeMind 中根据约定组织好的团队信息

  • 这样就令 XSLT 有基础原则可以进行数据组织了
  • 同理,也可以让Python 快速理解了...

注意:: 
XSLT ~ 以XML为形式的用来解析XML数据的样式语言,
嗯嗯嗯,好象很绕的语言,其实是XML应用领域中很实用的一个标准;
Xpath 就是XML世界中的查询语句,可以快速从XML数据岛中过滤出想要的信息;
在 PCS402 XML 中都有相关的介绍,读者可以深入了解一下.

当然 ElementTree !

在Python世界,处理XML解析的模块有N多种,小白从行者们那儿知道其中 ElementTree 最好用!

  • 不仅仅因为 ElementTree 好用到已经被收为官方标准包,而且支持 Xpath 的搜索

  • 其实从使用方面也是 ElementTreePythyonIc

  • deptorg.py 是根据对 .mm 的理解,快速组织的专用解析脚本,其中:

       1 from elementtree import ElementTree
       2 xmlFileName = "deptorg.xml"
       3 print open(xmlFileName,"r").read()
       4 tree = ElementTree.parse(xmlFileName)
       5 elem = tree.getroot()
       6 dept = elem.findall("node/node")
       7 for d in dept:
       8     print d
       9     print LI(d.attrib["TEXT"].encode("utf8"))
    
  • 解说
    1. 引入 ElementTree 模块

    2. 读入指定XML文件
    3. 处理XML文件为 ElementTree 对象

    4. 找到XML根
    5. 从根找到所有第二级的<node>节点对象列表

    6. 找到的所有节点,组织成一个列表,已经可以循环处理,并通过 .attrib["TEXT"] 来获得内容了!

注意:: 
"node/node" 就是个 Xpath 语句,
要求XML解析器,找出所有<node>标签中次一级的<node>标签节点!
在 PCS402 XML 中都有相关的介绍,读者可以深入了解一下.

图 KDay6-3 页面输出和XML文本对照情景

  • 就使用 findall()attrib["TEXT"] 便可以判别/获取足够的信息,从 .mm 中输出数据到页面凫!

  • 哈哈哈!具体的就请读者去看相应脚本吧...

通过SVN的下载地址:

详细:: 
PCS214 ElementTree
进一步介绍了这一因为自身的优秀品质,被吸收为内置模块的第三方XML处理模块;
分享了相关的常用操作体验;

在Karrigell中混合对象

但是! 怎么样可以在问卷系统中,使用 .mm 的理解成果?

  • 麻烦在于 ElementTree.parse() 需要XML文件的绝对访问路径

  • 然而,小白不想在程序中包含太多系统的真实路径信息,所以:
    1. 反复尝试,发现从相对路径偏移还是从URL 来访问都不靠谱
    2. 最简单的就是解析脚本和 .mm 文件在一起
  • 问题就来了,各种事务页面如何快捷直观的使用理解成果?
    • 自然的,就想到了 Include() Karrigell 中到处都可以进行的基础操作 -- 包含

Include("../xslmm/deptorg.py")  
            ^
            |
            +- 使用相对路径,回退一级;
            因为是从index.ks/login 之类下级行动函式页面访问
  • 小白根据MoinMoin 包含操作的经验,猜想这样一来应该能将字典对象混人当前名称空间?

    • 在专门的 .mm 解析脚本-- deptorg.py 中尝试 print dir()

图 KDay6-4 在页面输出当前名称空间的情景

  • 不会这么简单吧! 小白发现了本来应该只在 index.ks 事务页面中才有的对象 sess —— 对话容器

  • 继续尝试赋值一下子?在 deptorg.py 中最后追加..

    ...
    sess.usr["dept"]=deptall
    sess.usr["deptree"]=deptree
    
  • KAO! 真的好用! 在index.ks/stat 统计页面,包含 deptorg.py .mm专用解析脚本 再打印 sess.usr 看一下子,唉呀呀! 真的有了!

图 KDay6-5 在页面输出从 .mm 文件中解析出来的成员信息

成了!想要的数据从.mm解析出了,传递给页面成了,也测试打印出了,go on!

表单验证

进行回答统计前,必须有个先决重构

  • 有效的答案必须是全部问题都回答了,不然的话不仅涉及用户反复问答的情景,而且统计很不公平的说...
  • 但是曾经令小白激动的 JVF~JavaScript Validation Framework ... 忽然间怎么也不好用了!

    • 不得以,仔细看了一下子代码,才发现,有好几处设计是不能忍的:
      1. 所有表单都要读取同一配置XML,不论访问者想回答哪个问卷,导致动态生成配置文件时有争用问题
      2. 同样的原因,导致每次读取配置文件,可能要读入相对无用的几倍信息(别的表单的检验策略也被迫读取了)
      3. 在 validation-framework.js 中居然...

        try {
        var prefix = ["MSXML2", "MSXML", "Microsoft", "MSXML3"];
        for (var i = 0; i < prefix.length; i++) {
                //return new ActiveXObject(prefix[i] + ".DomDocument"); 
                var obj = new ActiveXObject(prefix[i] + ".DomDocument"); 
                if (obj == null || typeof(obj) == 'undefined') {
                        continue;
                } else {
                        return obj;
                }
        }
        } catch (e) {
        //^_^
        throw new Error("My God, What version of IE are you using? IE5&+ is requiered.");
        }
        

        啍啍...BS!居然不能支持 FireFox ,难道以前小白的成功反而是灵异现象?!这段js中, 使用了IE特有的ActiveXObject, 所以放到FireFox中就不能正常运行.

  • 小白彻底无言了!直接使用服务端的判定吧!那就直接在服务器端判断提交时是否已经回答全部问题。
  • 创立 bye() 判别处理页面

       1 def bye(**args):
       2     """不使用客户端验证后替代的服务端检验页面
       3     """
       4     print _htmhead("CPyUG·啄木鸟社区·自学问卷管理 obpKwDay6- Powered by Karrigell")
       5     print H1("%s"%(qcfg.desc.pname))
       6     qk = QUERY.keys()
       7     if "btn_submit" in qk:
       8         #print QUERY
       9         qp = DictIni(conf.qpage.qpath+sess.usr["qp"]+".cfg")
      10         qli = [i for i in qp.ask.keys()]
      11         a = []
      12         as = ""
      13         # 扫描并记录设计问卷的所有题目
      14         for k in qk:
      15             if "btn_submit"==k:
      16                 pass
      17             else:
      18                 as+=QUERY[k]+"\n"
      19                 a.append(k[6:])
      20         lost=[]
      21         qli.sort()
      22         # 对比,计数 已经回答的
      23         for q in qli:
      24             if q not in a:
      25                 lost.append(q)
      26 
      27         if 0==len(lost):
      28             answer = "%s%s.%s.aq"%(conf.qpage.apath
      29                                   ,sess.usr["qp"]#pubq.split(".")[-2]
      30                                   ,sess.usr["name"])
      31             open(answer,'w').write(as)
      32             ## 正常跳转到统计页面
      33             raise HTTP_REDIRECTION,"stat"
      34         print BR()*2
      35         print H4("没有回答完全!")
      36         for l in lost:
      37             print LI("问题%s 没有回答!"%l)
      38         print """<h4><input type="button"
      39             value="点击返回重新回答"
      40             class="btn"
      41             onClick="history.back();"/>
      42         </h4>
      43         """
      44     else:
      45         raise HTTP_REDIRECTION,"index"
      46     print htmfoot
    
    经过这样处理就可以判定问卷的所有题目是不是已经全部回答完了...
  • 接着如果判定失败时输出

    <input type="button"
        value="点击返回重新回答"
        class="btn"
        onClick="history.back();"/>
        

    这样的JS 行动按钮,快速完成想要的必须问答完全部问题之检验

图 KDay6-6 查觉有未回答问题时提醒的情景

  • 好了基本可用了,只是这样一来,加重了服务端的压力,也无法进行客户端页面的就地提醒...
    • 嗯嗯,自古忠孝难全,先这么着,以后再想了....

统计汇报

终于进入最后的功能完成了!小白已经开始志得意满起来了,因为,实在没有什么难度了卟?

  • 接下来的功能就单纯了:
    1. 把所有人的回答条目简单记录成一个文件, 格式为: 每行依次对应每个题目的回答
    2. 套用问卷列表的技巧,需要时可以搜索出对应问卷的所有成员回答记录
    3. 批量读到列表中,和成员信息字典匹配一下子,就可以人性化的输出了

相关代码:

   1 ali = fnmatch.filter(os.listdir(conf.qpage.apath), '%s.*.aq'%qpname)
   2 aed = []
   3 for f in ali:
   4     a = open(conf.qpage.apath+f,"r").read()
   5     aed.append(f.split(".")[-2])
   6 done = []
   7 unknow = []
   8 for a in aed:
   9     if a in sess.usr["dept"].keys():
  10         done.append(a)
  11     else:
  12         unknow.append(a)
注解
  • 先要预备处理一下子,过滤出不知道的非期待的回答者
  • 行1: 从目录中查找出所有相关回答记录文件(明显的小白约定回答文件的命名格式是 问卷名.成员帐号名.aq)

  • 行2~5: 根据文件名快速整理出名单列表 aed

  • 行6~12: 顺势,就有了已知合理成员回答列表 doneunknow 未知成员列表

图 KDay6-7 成员回答情况统计汇报总表情景

最最后就仅仅是信息的显示问题了:

  1. 想要有 回答时间的信息?

    fn = conf.qpage.apath+'%s.%s.aq'%(qpname,a)
    ...
    time.strftime("%y/%m/%d %H:%M:%S",time.localtime(os.path.getmtime(fn)
    
  2. 想要有 百分制 的成绩?

       1 def _grade(right,answer):
       2     """根据问卷答案自动计算分数
       3     """
       4     grade = 0
       5     for i in range(0,len(right)):
       6         if right[i]==answer[i]:
       7             grade +=1
       8         else:
       9             pass
      10     return 100*(float(grade)/len(right))
    
    • 唉呀呀?!小白突然发现,为什么不管怎么回答都是0分?
      • 原来, 保存答案的字典是无序的, 而回答是有顺序的! 这就造成了正确答案和回答的答案不匹配, 可以验证一下:

           1 print crtqp.ask.keys()
           2 crtright = [crtqp.ask[i]["key"] for i in crtqp.ask.keys()]
           3 print crtright
        

图 KDay6-8 将答案和问题教员检出到页面

  • 知道症结就好修改了, 直接排下序就好~

       1 #字典排序技巧
       2 ak = crtqp.ask.keys()
       3 ak.sort()
       4 print ak
       5 crtright = [crtqp.ask[i]["key"] for i in ak]
       6 print crtright
    

图 KDay6-9

  1. 要有总平均成绩?

    #使用stat 列表收集所有有效成绩
    stat = []
    ...
    stat.append(_grade(crtright,open(fn,"r").read().split()))
    ...
    sum(stat)/len(stat)) 就出来了
    reduce(lambda a,b:a+b, stat)/len(stat) 更加cool
    
  2. 哈哈哈! ! 什么也难不住小白!

图 KDay6-10 有总平均分的情景

事务测试

不用想什么黑/白盒; 边界,点击就成!

  • 好了! 所有功能都实现了,小白现在非常成就感的一路点击下来
  • 感觉哪里不好,随手就修正它!
  • 溜个几回,功能测试基本也就了了... ...

小结

今日,小白在昨日基础上,蒙头快速使用 .ks 模式服务页面,将原先实现了的功能全部归并到了两个文件中,进一步的:

  1. 成功!使用 .mm 文件作数据源来维护团队组织信息,并用包含的方式,应用到了页面事务中!
  2. 成功!发现了JVF 的设计缺陷,并通过判定事务的迁移,通过服务端判别,重构了功能的支持;
  3. 成功!综合过往成果完成了统计功能的支持;
  4. 本日成果

kday5/
|-- Karrigell_QuickForm.py  KQF快速表单模块
|-- dict4ini.py         Limodou 贡献的 ini 解析模块
|-- a                   回答收集目录
|   |-- easy051201.HD.aq        -实验问卷HD答案
|   |-- easy051201.Zoomq.aq     -实验问卷Zoomq答案
|   |-- easy051201.hoxide .aq   -实验问卷hoxide答案
|   `-- easy051201.perrin.aq    -实验问卷perrin答案
|-- q/                  问卷设计文本收藏目录
|   |-- CPUG051211.cfg  多问卷实例-CPyUG社区问卷
|   |-- CPUG051211.cfg.051225182951
|   |-- Python051221.cfg    -Python基础问卷
|   |-- Python051221.cfg.051225183000
|   |-- easy051201.cfg      -实验问卷草稿
|   |-- easy051201.cfg.051222112545 -实验问题编辑历史快照
|   |-- ...
|   `-- easy051201.cfg.051225162724
|-- index.ks            使用KS 模式重构的有登录功能的系统首页
|-- mana.ks             使用KS 模式重构问卷设计综合支持页面
|-- mana.pih            问卷设计页面
|-- qdesign.py          问卷设计实际行为脚本
|-- qpage.pih           问卷复审页面
|-- qpage.py            问卷复审实际行为脚本
|-- qprint.pih          问卷展示页面
|-- qprint.py           问卷展示实际行为脚本
|-- questionnaire.cfg   问卷系统配置文件
|-- questionnaire.tmpl  问卷展示模板
|-- tryKQF.py           KQF试用页面
`-- xslmm               freemind 思维图谱团队数据解析目录
    |-- deptorg.mm      团队定义 freemind 底稿文件
    |-- deptorg.py      团队定义 解析脚本
    |-- deptorg.xml     团队定义 输出XML数据文件
    |-- deptorganise.xsl团队定义 输出XSL模板
    |-- freemind.css    freemind 输出默认样式
    |-- freemind.xsl    freemind 输出默认模板
    |-- icons           freemind 输出图标目录
    |   |-- Mail.png
    |   |-- ...
    |   `-- xmag.png
    |-- hide.png        freemind 输出页面用小图形
    |-- ...
    `-- show.png

实例下载

使用SVN下载地址:

练习

  • 到目前为止, 我们已经实现了登录, 编辑, 显示, 删除, 搜索博客文章, 应该说作为个人博客已经差不多了, 当然我们还能继续扩展, 比如以下几点:
  • (1) 从单用户扩展为多个用户, 变为一个简单的博客系统;
  • (2) 黑白页面比较丑, 我们可以利用css做些页面的美化, 比如加入颜色, 布局调整等等;
  • (3) 思考:前述字典排序技巧是通过将字典关键词单独取出来成列表来排序的,有其它更加直接的方法嘛?

TODO


返回 KDays实例故事

::-- ZoomQuiet [2005-12-11 04:51:41]

Name Password4deL ;) :( X-( B-)