分别用Archetypes和Python编写产品

(转贴)分别用Archetypes和Python编写实现同一功能的产品,会发现这2种方法之间的许多异同。

(转贴)分别用Archetypes和Python编写实现同一功能的产品,会发现这2种方法之间的许多异同。

功能

这个产品主要实现文章的格式转换功能,提供3个页面:

一,编辑页面

在编辑页面中显示需要编辑的文档详细信息(有些信息是必填的),信息包括:

  1. ID:文档的名字(取名必须符合一定的规则)
  2. Title:文档的标题(必填)
  3. Group:文章所属的栏目(例如:头条,公告,专栏等等)
  4. Blurb:文章的摘要
  5. TextFormat:文章的转换格式(提供了5种转换格式)
  6. Body:文章的内容,提供2种输入方法(手动输入文章内容,上载文章内容)
  7. 对文档的2种操作(保存和取消)

二,查看页面

在查看页面中显示经过编辑后的文档内容:

  1. 文档的标题
  2. 可以对文档进行的一些操作(例如:发送给别人,打印文档等等)
  3. 文档所属的栏目
  4. 文档的描述(包括文档的摘要和经过格式转换后的文章内容)
  5. 文档的创建信息

三,属性页面

在属性页面中可以对文档的属性进行编辑(属性包括:生效日期,关键字,是否允许讨论,语言等等)

用Python编写这个产品

一,产品的属性

了解完这个产品需要实现那些功能后,可以大概推断出这个产品需要下面这些属性:

  1. ID:唯一的ID编号,这个属性是必须的。
  2. Title:标题,这个属性也是必须的。
  3. Description:描述信息,描述它作些什么。这个属性是可选的。
  4. Group:文章所属的栏目组。
  5. TextFormat:文章的转换格式。
  6. Cooked_Text:文章的内容。

ID, Title, Description:这3个属性已经在plone的Dublin Core中定义了,只需要在创建内容类型时加入基类来继承他的属性就可以了。我们只需要考虑如何实现文章的格式转换。

二,编写内容类型

首先需要创建一个目录用来存放这个产品的代码,把它命名为PyArticle,以区分后面用Archetypes编写的产品。然后在这个目录中加入需要的一些文件和目录:

  1. _init__.py:这个文件是用来指示这个目录是Python的软件包目录并可以被导入,导入软件包时Zope会执行这个文件,并对这个产品 进行注册。在后面的编写过程中要在这个文件中加入代码。
  2. readme.txt,install.txt:创建2个文本文件,可以让他们置空。它们的目的只是为了让代码对使用人员更友好些。
  3. refresh.txt:有这个文件就可以使用Zope的模块刷新功能,虽然它是空的。

三,格式转换

1. 编写执行格式转换的模块

我们可以把格式转换函数专门放在一个模块中,比如在这个产品目录下创建一个cook.py模块,在cook.py模块中写入格式转换函数:

from Products.CMFCore.utils import format_stx
from Products.PythonScripts.standard import html_quote

def cook_text(text, text_format):
""" Edit the text - Cook the body """
level = 1
if text_format == 'text/plain':
cooked_text = html_quote(text).replace('\n', '<br />')
elif text_format == 'text/html':
cooked_text = text
else:
cooked_text = format_stx(text=text, level=level)

def CookedBody(text, text_format, stx_level=None, setlevel=0):
""" Return the cooked text """
if (text_format=='text/html' or text_format=="text/plain'
or (stx_level is None)
or (stx_level == 1)):
return cooked_text
else:
cooked = format_stx(text, stx_level)
if setlevel:
cooked_text = cooked
return cooked

按照编写Python脚本的惯例,每个代码段中要加入一段测试代码,如果测试代码能够工作,这个语言有效:

if __name__ == ""__main__":
import sys
myself = sys.argv[0]
file = open(myself, 'r').read()
print CookedBody(file, stx_level=2)

编写好cook.py模块后,如果要进行格式转换,只需要导入这个模块,把一些必须的参数传递给格式转换函数,就可以实现格式的转换了。在这个过程必须注意不同模块之间函数参数的传递和同一模块之间函数参数传递的区别。

2. 把转换函数放在类定义中

也可以直接在类定义中编写格式转换函数,但是如果转换过程比较复杂,转换函数比较庞大,就容易使定义类的模块看上去过于杂乱。

四,编写类

把类命名为Article,在PyArticle目录下创建Article.py模块。首先要导入前面编写好的cook.py模块:

from cook import cook_text, CookedBody
1. 给类增加属性

id:保存PyArticle类型实例的唯一ID号 _articleGroup:保存文章所属的栏目 _textFormat:保存文章的转换格式 _cookedText:保存经过格式转换后的文章内容

2. 编写属性的获取函数

对于上面的每个属性编写一个获取函数,调用这个函数就返回对应的属性值(编写获取函数有利于对函数应用安全访问机制):

class Artcile:
def __init__(self, id):
self.id = id
self._articleGroup = ""
self._textFormat = ""
self._cookedText = ""

def getArticleGroup(self):
""" Return the group """
return self._articleGroup

def getTextFormat(self):
""" Return the textformat """
return self._textFormat

def getCookedText(self):
""" Return the cooked text """
return self._cookedText
3. 编写编辑函数

编写处理编辑函数edit,该函数读入group,text_format,text等信息,把group和text_format的值赋给 self._articleGroup和self._textFormat并确定文件名,并且把text_fomar,text的值传递给格式转换函数, 然后把格式函数返回的转换后的文章内容保存在self._cookedText属性中:

def edit(self, group, text_format, text, file="", safety_belt=""):
""" The function of editing the uploaded file """
if not group:
self._articleGroup = group = self.getArticleGroup()
self._articleGroup = group

if not text_format:
self._textFormat = text_format = self.getTextFormat()
self._textFormat = text_format

filename = ""
if file:
file_raw = file.read()
if file_raw:
text = file_raw
if hasattr(file, 'name'):
filename = file.name
else:
filename = file.filename

cook_text(text=text, text_format=text_format)
self._cookedText = CookedBody(text=text, text_format=text_format,stx_level=2)
4. 加入工厂函数

上面的代码已经组成了一个简单的软件包,但它不是一个Plone产品。必须用Plone对它进行初始化,就是说需要在Article.py中加入其他信息,特别是要加入一个工厂函数:

def addArticle(self, id, REQUEST=None):
""" Create an empty Article object """
obj = Article(id)
self._setObject(id, obj)

这个函数传递3个参数:self对象,对象的ID字符串,REQUEST。由于对象总是在容器对象中创建,self指向这个对象所属的容器.这个函 数创建一个名为obj的Article对象实例,并把它传递给容器的_setObject方法。_setObject是Zope中特有的,它在数据库中实 例化这个对象,并把对象注册到容器中。

5. 创建配置文件

创建一个配置文件config.py。在这个文件中可以定义一些重复使用的变量,例如产品名称,皮肤层次等等:

plone_product_name = "PythonArticle"
product_name = "PyArticle"
layer_name = "pyarticle"
layer_location = "PyArticle/skins"

还需要定义2个字典,存放产品供选择的group和textformat信息:

listArticleGroups = (
{'value':'headline', 'name':'Headline',},
{'value':'bulletin', 'name':'Special Bulletin',},
{'value':'column', 'name':'Weekly Column',},
{'value':'editorial', 'name':'Editorial',},
{'value':'release', 'name':'PressRelease',},
)

listTextFormats = (
{'value':'text/plain', 'name':'text/plain',},
{'value':'text/structured', 'name':'text/structured',},
{'value':'text/restructured', 'name':'text/restructured',},
{'value':'text/html', 'name':'text/html',},
{'value':'application/msword', 'name':'application/msword',},
)

回到Article.py模块加入2个方法,分别用来返回listArticleGroups和listTextFormats:

def getArticleGroups(self):
""" Return the list of article groups """
return listArticleGroups


def getTextFormats(self):
""" Return the list of text format """
return listTextFormats

再写入设置权限要用到的那些权限信息:

from Products.CMFCore import CMFCorePermissions

add_permission = CMFCorePermissions.AddPortalContent
edit_permission = CMFCorePermissions.ModifyPortalContent
view_permission = CMFCorePermissions.View

这个文件里面定义的变量在后面的代码中都有常用到

6. 构建工厂类型信息

在Article.py中设置工厂类型信息:

factory_type_information = {
'id': plone_product_name,
'meta_type': product_name,
'description': ('Provides a brief article format'),
'product': product_name,
'factory': 'addArticle',
'content_icon' : 'article.gif',
'immediate_view': 'view',
'actions': (
{'id': 'view',
'name': 'View',
'action': "article_view',
'permission': (view_permission,) },
{'id': 'edit',
'name': 'Edit',
'action': "article_edit_form',
'permission': (edit_permission,) },
{'id': 'properties',
'name': 'Properites',
'action': "article_base_metadata',
'permission': (edit_permission,) },
),
}

在里面的actions属性中定义了3种动作分别指向查看页面,编辑页面和属性页面。

7. 产品初始化

回到__init__.py文件,在里面写明要用到的类,构造器和工厂类型信息,再把这些信息传递给ContentInit函数,并进行产品注册:

import Article

from Products.CMFCore import utils
from Products.CMFCore.DirectyView import registerDirectory

from config import product_name, add_permission

contentConstructors = (Article.addArticle,)
contentClasses = (Article.Article,)
contentFTI = (Article.factory_type_information,)

registerDirectory("skins", globals())

def initialize(context):
product = utils.ContentInit(
product_name,
permission = add_permission,
extra_constructors = contentConstructors,
content_types = contentClasses,
fti = contentFTI,
)

product.initialize(context)
8. 改变产品模块
a. 导入变量

导入Plone初始化需要的变量:

from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from Products.CMFDefault.DublinCore import DefaultDublinCoreImpl
from Products.CMFCore.PortalContent import PortalContent

导入配置变量和权限:

from config import add_permission, edit_permission, view_permission
from config import plone_product_name, product_name
from config import listArticleGroups, listTextFormats

返回Article的类定义中,加入2个基类使它完全于Plone兼容,还要给类加入一个meta_type属性,Plone中每个产品必须有唯一的meta_type属性:

class Article(PortalContent, DefaultDublinCoreImpl):
meta_type = product_name

还要显示出这个内容类型实现的什么类:

__implements__ = (
PortalContent.__implements__,
DefaultDublinCoreImpl.__implements__
)

在类的__init__方法中加入:

DefaultDublinCoreImpl.__init__(self)
b. 给类加入安全控制

在Plone的对象发布环境中,任何人都可以通过web调用一个类的所有方法,除非它的名字是以下划线开始的。因此需要保护类中定义的方法,要保护方法需要在类中构建一个ClassSecurityInfo类的实例:

security = ClassSecurityInfo()

这个security对象提供Zope安全机制的接口,任何就可以应用这个对象的方法到类的每个方法中,可以在类的方法上面加上一个应用安全指示的语句,这样便于记住进行安全设置的位置。declareProtected方法使用权限和方法名为参数对给定的方法设定权限:

security.declareProtected(edit_permission, "edit")

这样就只有具有编辑权限的人才可以调用edit方法,应用这种方式给类中每个不是以下划线开始的方法设置合适的权限。要应用这些安全设置,还必须对类进行初始化,如果没有这一步,所有安全设置的声明都是无效的:

InitializeClass(Aticle)
c. 集成搜索引擎

因为Article类的基类设置为Portalontent(功能:每次改动对象类都会自动更新)和DefaultDublinCoreImpl (功能:自动为对象标题,描述,创建人,修改信息等建立索引),所以只需要创建一个SearchableText方法,用来返回和搜索内容匹配的信息(标 题,描述信息和代码)。并给这个方法设置view权限,使搜索的人能看到和搜索内容匹配的信息:

security.declareProtected(view_permission, "SearchableText")    
def SearchableText(self):
""" Return the information for indexing """
return "%s %s %s" % ( self.Title()
, self.Description()
, self._cookedText
)

五,编写好的模块

1. 类的Plone版本(Article.py)

一个Python产品与一个在Plone中注册了的产品有很大的差别,大部分的差别在于产品注册和插入安全控制方面,下面是Article类的Plone版本:

from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from Products.CMFDefault.DublinCore import DefaultDublinCoreImpl
from Products.CMFCore.PortalContent import PortalContent

from config import add_permission, edit_permission, view_permission
from config import plone_product_name, product_name
from config import listArticleGroups, listTextFormats

from cook import cook_text, CookedBody

factory_type_information = {
'id': plone_product_name,
'meta_type': product_name,
'description': ('Provides a brief article format'),
'product': product_name,
'factory': 'addArticle',
'content_icon' : 'article.gif',
'immediate_view': 'view',
'actions': (
{'id': 'view',
'name': 'View',
'action': "article_view',
'permission': (view_permission,) },
{'id': 'edit',
'name': 'Edit',
'action': "article_edit_form',
'permission': (edit_permission,) },
{'id': 'properties',
'name': 'Properites',
'action': "article_base_metadata',
'permission': (edit_permission,) },
),
}

def addArticle(self, id, REQUEST=None):
""" Create an empty Article object """
obj = Article(id)
self._setObject(id, obj)

class Article(PortalContent, DefaultDublinCoreImpl):
meta_type = product_name

__implements__ = (
PortalContent.__implements__,
DefaultDublinCoreImpl.__implements__
)

security = ClassSecurityInfo()

def __init__(self, id):
DefaultDublinCoreImpl.__init__(self)
self.id = id
self._articleGroup = ""
self._textFormat = ""
self._cookedText = ""

security.declareProtected(edit_permission, "edit")
def edit(self, group, text_format, text, file="", safety_belt=""):
""" The function of editing the uploaded file """
if not group:
self._articleGroup = group = self.getArticleGroup()
self._articleGroup = group

if not text_format:
self._textFormat = text_format = self.getTextFormat()
self._textFormat = text_format

filename = ""
if file:
file_raw = file.read()
if file_raw:
text = file_raw
if hasattr(file, 'name'):
filename = file.name
else:
filename = file.filename

cook_text(text=text, text_format=text_format)
self._cookedText = CookedBody(text=text, text_format=text_format,stx_level=2)

security.declareProtected(view_permission, "getArticleGroup")
def getArticleGroup(self):
""" Return the group """
return self._articleGroup

security.declareProtected(view_permission, "getTextFormat")
def getTextFormat(self):
""" Return the textformat """
return self._textFormat

security.declareProtected(view_permission, "getCookedText")
def getCookedText(self):
""" Return the cooked text """
return self._cookedText

security.declareProtected(view_permission, "getArticleGroups")
def getArticleGroups(self):
""" Return the list of article groups """
return listArticleGroups

security.declareProtected(view_permission, "getTextFormats")
def getTextFormats(self):
""" Return the list of text format """
return listTextFormats

security.declareProtected(view_permission, "SearchableText")
def SearchableText(self):
""" Return the information for indexing """
return "%s %s %s" % ( self.Title()
, self.Description()
, self._cookedText
)


InitializeClass(Aticle)
2. 格式转换模块(cook.py)

下面是编写好的cook.py模块:

from Products.CMFCore.utils import format_stx
from Products.PythonScripts.standard import html_quote

def cook_text(text, text_format):
""" Edit the text - Cook the body """
level = 1
if text_format == 'text/plain':
cooked_text = html_quote(text).replace('\n', '<br />')
elif text_format == 'text/html':
cooked_text = text
else:
cooked_text = format_stx(text=text, level=level)

def CookedBody(text, text_format, stx_level=None, setlevel=0):
""" Return the cooked text """
if (text_format=='text/html' or text_format=="text/plain'
or (stx_level is None)
or (stx_level == 1)):
return cooked_text
else:
cooked = format_stx(text, stx_level)
if setlevel:
cooked_text = cooked
return cooked


if __name__ == ""__main__":
import sys
myself = sys.argv[0]
file = open(myself, 'r').read()
print CookedBody(file, stx_level=2)
3. 配置文件(config.py)

下面是配置文件config.py:

from Products.CMFCore import CMFCorePermissions

add_permission = CMFCorePermissions.AddPortalContent
edit_permission = CMFCorePermissions.ModifyPortalContent
view_permission = CMFCorePermissions.View

plone_product_name = "PythonArticle"
product_name = "PyArticle"
layer_name = "pyarticle"
layer_location = "PyArticle/skins"

listArticleGroups = (
{'value':'headline', 'name':'Headline',},
{'value':'bulletin', 'name':'Special Bulletin',},
{'value':'column', 'name':'Weekly Column',},
{'value':'editorial', 'name':'Editorial',},
{'value':'release', 'name':'PressRelease',},
)

listTextFormats = (
{'value':'text/plain', 'name':'text/plain',},
{'value':'text/structured', 'name':'text/structured',},
{'value':'text/restructured', 'name':'text/restructured',},
{'value':'text/html', 'name':'text/html',},
{'value':'application/msword', 'name':'application/msword',},
)
4. __init__.py

下面是__init__.py模块:

import Article

from Products.CMFCore import utils
from Products.CMFCore.DirectyView import registerDirectory

from config import product_name, add_permission

contentConstructors = (Article.addArticle,)
contentClasses = (Article.Article,)
contentFTI = (Article.factory_type_information,)

registerDirectory("skins", globals())

def initialize(context):
product = utils.ContentInit(
product_name,
permission = add_permission,
extra_constructors = contentConstructors,
content_types = contentClasses,
fti = contentFTI,
)

product.initialize(context)

六,添加皮肤

写好主要代码之后,还要给产品编写皮肤和安装方法。在产品目录PyArticle下添加skins目录。皮肤就放置在skins目录里,这个路径名 在__init__.py文件中已经定义,并且在__init__.py文件中使用了registerDirectory函数进行了注册。

1. 设置产品图标

为PyArticle对象加一个在Plone中显示的小图标,图标名称已经在工厂类型信息中定义了('content_icon': "article.gif'),所以只需要在skins目录下加一个article.gif小图标文件,在Plone用户界面中能看见PyArticle 对象的地方就能看见这个图标。

2. 制作查看页面

从这个产品查看页面需要显示的内容,发现它类似于页面文档的查看页面。可以把页面文档的查看页面拷贝到skins目录下,把它命名为 article_view.pt然后再进行修改和调整,页面文档的查看页面document_view.pt在 CMFPlone/skins/plone_content目录下面,需要对几个地方修改。

a. 修改len_text定义

把len_text的定义改成:

tal:define="text here/getCookedText;
len_text python:len(text);"
b. 显示Group内容

把原来显示Description的段落去掉,加上显示Group的段落:

<div class="field"
tal:condition="len_text">
<label>Group</label>
<div tal:content="here/getArticleGroup">Group</div>
</div>
c. 显示Blurb内容

在页面原来显示文本内容的段落上面加上显示Blurb的段落:

<div class="field"
tal:condition="len_text">
<label>Blurb</label>
<div tal:content="here/Description">Blurb</label>
</div>
d. 修改显示文本内容段落

把页面文档的CookedBody属性替换成._cookedText属性,这是页面文档显示文本内容的语句:

<div tal:replace="structure python:here.CookedBody(stx_level=2)" />

替换成:

<div tal:replace="structure here/getCookedText" />
3. 制作编辑页面

把页面文档的编辑页面document_edit_form.cpt复制到skins目录下,命名为article_edit_form.cpt需要修改下面几个地方

a. 显示选择Group段落

在页面文档编辑页面中提示输入摘要的段落前面加入提示输入Group的段落,引用类定义中的getArticleGroups函数,以下拉列表的方式显示供选择的栏目组:

<div class="field"
tal:define="tabindex tabindex/next;
group python:request.get('group', getattr(here, 'group', 'Headline'))">
<label for="group">Group</label>

<div class="forHelp" i18n:translate="help_group">
Select the name of group the text belonged to
</div>
<select id="group" name="group">
<option tal:repeat="item here/getArticleGroups"
tal:content="item/name"
tal:attributes="value item/name" />
</select>
</div>
b. 显示Blurb输入段落

只需要把原来显示Description输入段落中的:

<label for="description"
i18n:translate="label_description">Description</label>

改成:

<label for="blurb"
i18n:translate="label_blurb">Blurb</label>
c. 显示选择TextFormat段落

在编辑页面中的:

<label for="text" i18n:translate="label_body_text">Body text</label>

后面加上显示选择TextFormat的段落,引用类定义中的getTextFormats函数,以下拉列表的方式显示供选择的转换格式:

<div class="field"
style="text-align:right; margin-right:0.75em;">
<label for="text_format">Text Format</label>

<select id="text_format" name="text_format">
<option tal:repeat="item here/getTextFormats"
tal:content="item/name"
tal:attributes="value item/name" />
</select>
</div>

并去掉页面文档编辑页面中的转换格式选择段落:

<div metal:use-macro="here/wysiwyg_support/macros/textFormatSelector">
Format box, if needed
</div>
4. 制作属性页面

把页面文档的属性页面拷贝到skins目录下,命名为article_base_metadata.cpt就可以了,不需要任何修改。

5. 设置验证器和表单的动作

在skins目录下创建article_edit_form.cpt.metadata文件,在文件里设置表单的验证器和动作:

[default]
title=PyArticle editing form

[validators]
validators..Save=validate_id, validate_title
validators..Cancel=

[actions]
action.failure=traverse_to:string:article_edit_form
action.success..Save=traverse_to:string:artucle_edit
action.success..Cancel=redirect_to_action:string:view

这个文件给编辑表单设置了2个验证器,并且指定了验证后表单应该执行那些动作:验证失败就返回编辑页面。验证成功后如果保存就执行编辑脚本,如果取消就返回查看页面

6. 编写编辑脚本

把页面文档的编辑脚本document_edit.cpy(在CMFPlone/skins/plone_form/scripts目录下)拷贝到skins目录下,命名为article_edit.cpy需要修改2个地方:

##parameters=text_format, text, file='', SafetyBelt='', title='', description='', id=''

在参数text_format前面增加一个参数group:

##parameters=group, text_format, text, file='', SafetyBelt='', title='', description='', id=''

并在调用edit函数时,把参数group也加进去:

new_context.edit( text_format, text, file, safety_belt=SafetyBelt)

改成:

new_context.edit( group, text_format, text, file, safety_belt=SafetyBelt)

然后把document都改成PyArticle,编辑脚本提取在编辑页面中输入的值,并以这些值为参数调用类定义中的edit函数,进行文本格式 的转换,还需要创建一个文件给这个编辑脚本指明验证器和后面的动作,在skins所目录下创建article_edit.cpy.metadata文件, 输入下面的代码:

[validators]
validators..Save=validate_id, validate_title
validators..Cancel=

[actions]
action.failure=redirect_to_action:string:edit
action.success=redirect_to_action:string:view

七,编写安装函数

把一个产品安装到Plone中有一个标准的方法,就是使用portal_quickinstaller工具进行安装,而要使用Quick installer必须创建Extensions目录,在这个目录下编写Install.py模块,这个模块必须定义一个install函数,这样 Quick installer工具就会执行这个函数,install方法会把产品安装到portal types中,并把产品的FSDV加入到皮肤目录中去,下面是安装函数的全部代码:

from Products.CMFCore.DirectoryView import createDirectoryView
from Products.CMFCore.utils import getToolByName
from Products.CMFCore.TypesTool import FactoryTypeInformation as fti_klass

from Products.PyArticle.config import plone_product_name, product_name
from Products.PyArticle.config import larer_name, layer_location

def install(self):
""" Install this product """
out = []
typesTool = getToolByName(self, 'portal_types')
skinsTool = getToolByName(self, 'portal_skins')

if id not in typesTool.objectIds():
typesTool.manage_addTypeInformation(
fti_klass.meta_type,
id = plone_product_name,
typeinfo_name = "%s: %s" % (product_name, product_name)
)
out.append('Registered with the types tool')
else:
out.append('Object "%s" already existed in the types tool' % (id))

# add in the directory view pointing to our skin
if layer_name not in skinsTool.objectIds():
createDirectoryView(skinsTool, layer_location, layer_name)
out.append('Added "%s" directory view to portal_skins' % layer_name)

# add in the layer to all our skins
skins = skinsTool.getSkinSelections()
for skin in skins:
path = skinsTool.getSkinPath(skin)
path= [ p.strip() for p in path.split(',') ]
if layer_name not in path:
path.insert(path.index('custom')+1, layer_name)

path = ", ".jpin(path)
skinsTool.addSkinSelection(skin, path)
out.append('Added "%s" to "#s" skins' % (layer_name, skin))
else:
out.append('Skipping "%s" skin' % skin)

return "\n".join(out)

各种产品的安装方法几乎都一样,只需要修改里面的导入模块名字,上面的代码就几乎可以实现任何产品的安装功能。

利用Archetypes工具编写产品

用Archetypes编写产品的主要部分在于用Python对内容形态进行描述,写好描述内容后Archetypes会自动做许多事情,象建立显示页面和编辑表单。

一, 编写内容类型

把用Archetypes编写的产品命名为AtArticle,和用Python编写产品一样先加入4个空文件readme.txt,install.txt,refresh.txt 和__init__.py

1. 用Python描述内容形态

在AtArticle目录下建立Article.py文件,先定义这个产品的框架结构(schema),schema的内容决定了编辑页面的显示内 容。框架一般由2部分组成:一部分是Archetypes提供的BaseSchema(含有plone中必备的2个元素:ID,Title)。另外一部分 就是产品自身定义的schema。schema的组成单元是field,Archetypes提供了许多类型的field,可以根据需要显示的内容类型选 取相应的field,所以在这个schema中之需要加入3个field分别用于显示Group,Blurb和Body,每一个field都有许多参数, 其中name是必备的。field的widget参数用来显示对应的一块内容,有许多种widget可以选择,根据所需要的显示面貌选择不同的 widget.最后还需要设定主栏位的类型marshall=PrimaryFieldMarshaller():

schema = BaseSchema + Schema((
StringField('group',
vocabulary = ARTICLE_GROUPS,
widget = SelectionWidget(),
),
StringField('blurb',
searchable = 1,
widget = TextAreaWidget(),
),
TextField('body',
searcheable = 1,
required = 1,
primary = 1,
default_output_type = 'text/html',
allowable_content_types = ( 'text/plain',
'text/structured',
'text/restructured',
'text/html',
'application/msword'),
wigdet = RichWidget(label='Body'),
),
),
marshall = PrimaryFieldMarshaller(),
)

其中Body是一个复杂的内容类型,在定义里面searchable属性指明了这个field是否可是被搜索到。primary属性指明是否能够回应FTP和WebDAV,在这个field里还可以上传多种转换格式的内容,并且设定了默认的转换格式。

2. 定义内容类型

为了能够在Archetypes中实现重载,必须要为这个内容类型创建一个叫BaseContent的基类。然后引用上面定义好的schema。 Archetypes会自动生成查看,编辑和属性页面。如果要用自己编写的页面,需要进行重载。如果要用自己的查看页面,可以通过在类型定义中设置FTI 中的actions属性来实现,在actions中指明动作和要用到的页面,当然需要创建相应的页面(FTI)中的所有属性都可以被重载),最后注册产品 和对象:

class Article(BaseContent):
schema = schema
actions = ({
'id': 'view',
'name': 'View',
'action': 'string:${object_url}/article_view',
'permission': (CMFCorePermissions.View,)
},)

registerType(Article,PROJECTNAME)
3. 编写配置文件

和用Python编写产品一样,在AtArticle目录下创建config.py在这个文件里面定义一些经常重复使用的变量:

from Products.CMFCore.CMFCorePermissions import AddPortalContent
from Products.Archetypes.public import DisplayList

ADD_CONTENT_PERMISSION = AddPortalContent
PROJECTNAME = "AtArticle"
SKINS_DIR = "skins"

GLOBALS = globals()

ARTICLE_GROUPS = DisplayList((
('headline', 'Headline'),
('bulletin', 'Sepecial Bulletin'),
('column', 'Weekly Column'),
('editorial', 'Editorial'),
('release', 'Press Release'),
))
4. 导入Archetypes提供的类

Archetypes工具自带了所多模块,在这些模块里面定义了许多类,导入这些具有强大功能的类可以让编写产品的工作变的很轻松:

from Products.Archetpes.public import BaseSchema, Schema
from Products.Archetpes.public import StringField, TextField
from Products.Archetpes.public import SelectionWidget, TextAreaWidget
from Products.Archetpes.public import RichWidget
from Products.Archetpes.public import BaseContent, registerType
from Products.Archetpes.Marshall import PrimaryFieldMarshaller
from Products.CMFCore import CMFCorePermissions
from config import ARTICLE_GROUPS, PROJECTNAME
5. 编写好的Article.py

下面是完整的Article.py代码:

from Products.Archetpes.public import BaseSchema, Schema
from Products.Archetpes.public import StringField, TextField
from Products.Archetpes.public import SelectionWidget, TextAreaWidget
from Products.Archetpes.public import RichWidget
from Products.Archetpes.public import BaseContent, registerType
from Products.Archetpes.Marshall import PrimaryFieldMarshaller
from Products.CMFCore import CMFCorePermissions
from config import ARTICLE_GROUPS, PROJECTNAME

schema = BaseSchema + Schema((
StringField('group',
vocabulary = ARTICLE_GROUPS,
widget = SelectionWidget(),
),
StringField('blurb',
searchable = 1,
widget = TextAreaWidget(),
),
TextField('body',
searcheable = 1,
required = 1,
primary = 1,
default_output_type = 'text/html',
allowable_content_types = ( 'text/plain',
'text/structured',
'text/restructured',
'text/html',
'application/msword'),
wigdet = RichWidget(label='Body'),
),
),
marshall = PrimaryFieldMarshaller(),
)


class Article(BaseContent):
schema = schema
actions = ({
'id': 'view',
'name': 'View',
'action': 'string:${object_url}/article_view',
'permission': (CMFCorePermissions.View,)
},)

registerType(Article,PROJECTNAME)
6. 编写初始化文件

在__init__.py中加入下面这些代码,执行初始化产品的功能:

from Products.Archetypes.public import process_types, listTypes
from Products.CMFCore import utils
from Products.CMFCore.DirectoryView import registerDirectory

from config import SKINS_DIR, GLOBALS, PROJECTNAME
from config import ADD_CONTENT_PERMISSION

registerDirectory(SKIS_DIR, GLOBALS)

def initialize(context):
import Article

content_types, constructors,ftis = process_types(
listTypes(PROJECTNAME),
PROJECTNAME)

utils.ContentInit(
PROJECTNAME + " Content ",
content_types = content_types,
permission = ADD_CONTENT_PERMISSION,
extra_constructors = constructors,
fti = ftis,
).initialize(context)

二, 编写皮肤

在AtArticle目录下创建skins目录,在这个目录下加入你要重载的查看页面article_view.pt:

<html xmlns="http://www.w3.org/1999/xhtml" cml:lang="en-US"
lang="en-US"
metal:use-macro="here/main_template/macros/master" >
<body>
<div metal:fill-slot="main">
<div metal:define-macro="main"
tal:omit-tag="">
<h1 tal:content="here/title_or_id" />

<tal:block tal:define="field_macro field_macro|here/widgets/field/macros/view;
mode_string:view;
uce_label python:1" >
<div tal:repeat="field python:here.Schema()>filterFields(isMetadata=0)" >
<span tal:condition="python:field.getName() not in ['body'] " >
<span metal:use-macro="field_macro" />
</span>
</div>
</tal:block>
<span tal:content="structure python:here.getBody(mimetyoe='text/html')" />
</div>
</div>
</body>
</html>

三. 编写安装函数

在产品目录下创建Entensions目录,在这个目录中编写安装产品的脚本Install.py:

from Products.Archetypes.public import listTypes
from Products.Archetypes.Extentions.utils import installTypes, install_subskin
from Products.AtArticle.config import PROJECTNAME, GLOBALS

from StringIO import StringIO

def install(self):
out = StringIO()
installTypes(self, out, listTypes(PROJECTNAME), PROJECTNAME)
install_subskin(self, out, GLOBALS)
out.write("Successfully installed %s." % PROJECTNAME)
return out.getvalue()

两种方法的异同

一. 有区别的地方

分别用2种方法编写完实现同一功能的产品后,发现有下面几点主要的区别:

  1. Archetypes会自动建立视图页面和动作,依据是内容形态中的schema描述和Archetypes本身自带的一套standard requirements。用Python则需要自己编写,而这些页面模块和脚本模块正是比较容易出错和麻烦的那一部分。
  2. Archetypes提供了文本格式转换的功能。而用Python则需要自己编写这些格式转换函数。
  3. Archetypes提供了帮助安装产品的一些模块,在安装产品模块Install.py中,只需要很少的代码就实现了产品的安装功能。用Python编写的安装产品模块中,代码要复杂许多。
  4. Archetypes自带标准的安全设定,用Python编写必须自己设定。

二. 相同之处

  1. 都需要定义内容类型,只不过Archetypes自带了许多功能强大的模块,使得定义过程变的简单轻松。
  2. 都有配置文件和初始化文件,文件里面的内容也差不多
  3. 都要写皮肤和安装产品模块,如果用Archetypes不打算重载页面就不需要编写皮肤。