又一个由Makina Corpus编写的WSGI Paste工厂
项目描述
CGWB是paster的Web界面,其目标是生成一个从一组模板聚合的选项的Web界面。
想象一下,您有两个模板,一个可以部署应用程序,另一个可以生成应用程序本身。
将这两个模板声明为 cgwb 集合 将为这两个模板创建一个 web 接口。正确回答问题将生成一个 tarball,您可以下载并解压以设置基本安装。
要使模板可用,您必须使用 ZCML 定义该集合。
由于该服务器被开发为 paster 的快速和高效接口,不建议将其暴露在互联网上。 为了安全起见,仅在需要时启动/使用。
下一个版本将包括一些会话/角色和改进的安全功能,这个阶段可能可以将其保持开放。
请在此处查看实际效果 here
致谢
公司
安装
在minitage中安装或更新cgwb
即使它至少是运行 plones 模板推荐的模式,您也不必使用 minitage。
假设您的 minitage 位于 ~/minitage,请执行以下操作
export MT=~/minitage
如果有专用虚拟环境,请在其中安装或更新minitage
只需这样做(您必须参考 minitage 安装 的先决条件)
安装 virtualenv
virtualenv --no-site-packages --distribute $MT
更新 minitage 包
source $MT/bin/activate easy_install -U minitage.core easy_install -U minitage.paste minimerge -s
安装cgwb
通过 minibuild 下载和安装
source $MT/bin/activate git clone http://github.com/collective/collective.generic.webbuilder-minilay.git $MT/minilays/cgwb minimerge -v cgwb
Cgwb 位于 $MT/bfg/cgwb。
使用minitage生成和部署您的项目
启动cgwb服务器
通过 bin/cgwb 启动。此二进制文件包含一些选项,允许您覆盖默认端口(–port)和监听地址(–host)。要查看所有可用选项,只需使用
bin/cgwb --help
如果您使用 minitage,则必须使用 minitage.instances.env 配置文件
$MT/bin/easy_install -U minitage.paste $MT/bin/paster create -t minitage.instances.env cgwb
MINITAGE .ENV
每次您使用 cgwb 时,您都使用 .ENV
source $MT/bfg/cgwb/sys/share/minitage/minitage.env
使用它
启动它
cd $INS ./bin/cgwb --port=6253
目前,cgwb 没有会话机制,因此重放生成的唯一方法是使用 selenium firefox 插件。
如果您想将您的选择存储起来以便稍后重新生成更新后的 tarball,只需安装 SeleniumIDE firefox 插件并使用它来记录您的会话。
也许,激活 selenium 并
转到 cgwb
选择 Generic Portal Plone3。
填写设置,一些说明
项目名称是必填的,并且必须采用 project 或 subproject 的形式。
您可以在 Plone Products to auto checkout in development mode 中选择从社区中自动检出并用于开发模式的产品。
关于启动项目的重点部分
如果您有一些 minitage 经验,最好在构建之前对代码进行版本控制,因为 minitage 更新机制。
在将代码导入您的 SCM 之前,您必须避免以下问题
默认情况下,生成的 tarball 包含构建 out 布局和 src 中的所有 eggs,并且构建 out 将它们用作开发 eggs 而不是 WITH MR.DEVELOPER。因此,要在独立模式下运行构建 out
您可以选择不包含它们作为是,而是将代码分开并在其他地方对代码进行版本控制。
我建议您使用 mr.developer 检出包。
使用svn和generic/pyramid的示例
对于使用 subversion 作为我的 SCM,从生成的 tarball 中进行操作,可以产生此布局
import |-- import/eggs | |-- import/eggs/myproject.core | | `-- import/eggs/myproject.core/trunk `-- import/buildout
导出基本变量
export PROJECT="myproject" # your project name as filled in the web interfacE export TARBALL="$(ls -1t ~/cgwb/${PROJECT}-*.tar.gz|head -n1)" # produced tarball export IMPORT_URL="https://subversion.xxx.net/scrumpy/${PROJECT}/" # base svn place to import
创建一个临时工作空间
mkdir -p $PROJECT/tarball cd $PROJECT tar xzvf $TARBALL -C tarball/
创建要导入的基本布局
mkdir -p import/buildout import/eggs
将生成的 plone 扩展 eggs 移动到单独的位置以导入
for i in tarball/src/${PROJECT}*;do if [[ -d $i ]] && [[ $(basename $i) != "themes" ]];then j=$(basename $i);dest=import/eggs/$j/trunk; mkdir -pv $(dirname $dest); mv -v $i $dest; fi; done
将构建 out 结构移到导入布局中
cp -rf tarball/* import/buildout
更新构建 out 以使用 mr.developer 而不是基本开发
* move off the develop declaration:: sed -re "s:(src/)?$PROJECT\.((skin)|(tma)|(core)|(testing))::g" -i import//buildout/etc/project/$PROJECT.cfg * add to mr.developer sources:: sed -re "/\[sources\]/{ a $PROJECT.core = svn $IMPORT_URL/eggs/$PROJECT.core/trunk }" -i import/buildout/etc/project/sources.cfg * add to auto checkout packages:: sed -re "/auto-checkout \+=/{ a \ $PROJECT.core }" -i import/buildout/etc/project/sources.cfg sed -re "/eggs \+=.*buildout:eggs/{ a \ $PROJECT.core }" -i import/buildout/etc/project/$PROJECT.cfg sed -re "/zcml \+=/{ a \ $PROJECT.core }" -i import/buildout/etc/project/$PROJECT.cfg
确保使用正确的 svn url 进行检出
sed -re "s|src_uri.*|src_uri=$IMPORT_URL/buildout/|g" -i import/buildout/minilays/$PROJECT/*
确保使用 svn
sed -re “s|src_type.*|src_type=svn|g” -i import/buildout/minilays/$PROJECT/*
导入
svn import import/ $IMPORT_URL -m "initial import"
使用svn和generic/plone的示例
对于使用 subversion 作为我的 SCM,从生成的 tarball 中进行操作,可以产生此布局
import |-- import/eggs | |-- import/eggs/myproject.policy | | `-- import/eggs/myproject.policy/trunk | |-- import/eggs/myproject.skin | | `-- import/eggs/myproject.skin/trunk | |-- import/eggs/myproject.testing | | `-- import/eggs/myproject.testing/trunk | `-- import/eggs/myproject.tma | `-- import/eggs/myproject.tma/trunk `-- import/minitage |-- import/minitage/buildouts | `-- import/minitage/buildouts/zope | `-- import/minitage/buildouts/zope/myproject
导出基本变量
export PROJECT="myproject" # your project name as filled in the web interfacE export TARBALL="$(ls -1t ~/cgwb/${PROJECT}-*.tar.gz|head -n1)" # produced tarball export IMPORT_URL="https://subversion.xxx.net/scrumpy/${PROJECT}/ # base svn place to import
创建一个临时工作空间
mkdir -p $PROJECT/tarball cd $PROJECT tar xzvf $TARBALL -C tarball/
创建要导入的基本布局
mkdir -p import/buildout import/eggs
将生成的 plone 扩展 eggs 移动到单独的位置以导入
for i in tarball/src/${PROJECT}*;do if [[ -d $i ]] && [[ $(basename $i) != "themes" ]];then j=$(basename $i);dest=import/eggs/$j/trunk; mkdir -pv $(dirname $dest); mv -v $i $dest; fi; done
将构建 out 结构移到导入布局中
cp -rf tarball/* import/buildout
更新构建 out 以使用 mr.developer 而不是基本开发
* move off the develop declaration:: sed -re "s:(src/)?$PROJECT\.((skin)|(tma)|(policy)|(testing))::g" -i import//buildout/etc/project/$PROJECT.cfg * add to mr.developer sources:: sed -re "/\[sources\]/{ a $PROJECT.policy = svn $IMPORT_URL/eggs/$PROJECT.policy/trunk a $PROJECT.tma = svn $IMPORT_URL/eggs/$PROJECT.tma/trunk a $PROJECT.skin = svn $IMPORT_URL/eggs/$PROJECT.skin/trunk a $PROJECT.testing = svn $IMPORT_URL/eggs/$PROJECT.testing/trunk }" -i import/buildout/etc/project/sources.cfg * add to auto checkout packages:: sed -re "/auto-checkout \+=/{ a \ $PROJECT.policy a \ $PROJECT.tma a \ $PROJECT.skin a \ $PROJECT.testing }" -i import/buildout/etc/project/sources.cfg sed -re "/eggs \+=.*buildout:eggs/{ a \ $PROJECT.policy a \ $PROJECT.tma a \ $PROJECT.skin a \ $PROJECT.testing }" -i import/buildout/etc/project/$PROJECT.cfg sed -re "/zcml \+=/{ a \ $PROJECT.policy a \ $PROJECT.tma a \ $PROJECT.skin }" -i import/buildout/etc/project/$PROJECT.cfg
确保使用正确的 svn url 进行检出
sed -re "s|src_uri.*|src_uri=$IMPORT_URL/buildout/|g" -i import/buildout/minilays/$PROJECT/*
确保使用 svn
sed -re “s|src_type.*|src_type=svn|g” -i import/buildout/minilays/$PROJECT/*
导入
svn import import/ $IMPORT_URL -m "initial import"
使用git和generic的示例
对于使用 subversion 作为我的 SCM,从生成的 tarball 中进行操作,可以产生此布局
import |-- myproject.policy |-- myproject.skin |-- myproject.testing `-- myproject.tma `-- myproject.buildout `-- myproject.minilay
导出基本变量
export PROJECT="myproject" # your project name as filled in the web interfacE export TARBALL="$(ls -1t ~/cgwb/${PROJECT}-*.tar.gz|head -n1)" # produced tarball export IMPORT_URL="ssh://git.makina-corpus.net/var/git" # base svn place to import
创建临时工作空间和要导入的基本布局
mkdir -p $PROJECT/ cd $PROJECT mkdir tarball import tar xzvf $TARBALL -C tarball/
将生成的 plone 扩展 eggs 移动到单独的位置以导入
for i in tarball/src/*;do if [[ -d $i ]] && [[ $i != "tarball/src/themes" ]];then j=$(basename $i);dest=import/$j;mkdir -pv $(dirname $dest); mv -v $i $dest; fi; done
将构建 out 结构移到导入布局中
cp -rf tarball/minilays/$PROJECT import/$PROJECT.minilay rm -rf tarball/minilays cp -rf tarball/ import/$PROJECT.buildout
更新构建 out 以使用 mr.developer 而不是基本开发
* move off the develop declaration:: sed -re "s:(src/)?$PROJECT\.((skin)|(tma)|(policy)|(testing))::g" -i import/$PROJECT.buildout/etc/project/$PROJECT.cfg * add to mr.developer sources:: sed -re "/\[sources\]/{ a $PROJECT.policy = git $IMPORT_URL/$PROJECT.policy a $PROJECT.tma = git $IMPORT_URL/$PROJECT.tma a $PROJECT.skin = git $IMPORT_URL/$PROJECT.skin a $PROJECT.testing = git $IMPORT_URL/$PROJECT.testing }" -i import/$PROJECT.buildout/etc/project/sources.cfg * add to auto checkout packages:: sed -re "/auto-checkout \+=/{ a \ $PROJECT.policy a \ $PROJECT.tma a \ $PROJECT.skin a \ $PROJECT.testing }" -i import/$PROJECT.buildout/etc/project/sources.cfg sed -re "/eggs \+=.*buildout:eggs/{ a \ $PROJECT.policy a \ $PROJECT.tma a \ $PROJECT.skin a \ $PROJECT.testing }" -i import/$PROJECT.buildout/etc/project/$PROJECT.cfg sed -re "/zcml \+=/{ a \ $PROJECT.policy a \ $PROJECT.tma a \ $PROJECT.skin }" -i import/$PROJECT.buildout/etc/project/$PROJECT.cfg
确保使用正确的 git url 进行检出
sed -re "s|src_uri.*|src_uri=$IMPORT_URL/$PROJECT.buildout|g" -i import/*.minilay/*
确保使用 git
sed -re “s|src_type.*|src_type=git|g” -i import/.minilay/
导入
pushd import;for i in *;do echo "Importing $i";pushd $i;git init;git add *;git commit -am "initial revision";git remote add origin "$IMPORT_URL/$i";git push --all origin;popd;done;popd
部署项目
安装 minilay
export MT=~/minitage svn co $IMPORT_URL/buildout/minilays/$PROJECT/ $MT/minilays/$PROJECT # or git clone $IMPORT_URL/$PROJECT.minilay $MT/minilays/$PROJECT
安装它
minimerge -v $PROJECT
测试和文档
通过ZCML定义集合
一组是模板的集合,也称为“PasterConfiguration”。
------------------------------------------- | configuration | | | | ----------------------------------- | | templates | | ----------------------------------- | | | group | | | [-------------------------- | | | | options | | | | -------------------- | | | | | -------------------------------------------
我们将以一个“众所周知的”plone模板为例进行重新定义。
首先,我们需要定义一个模板
>>> from zope.configuration import xmlconfig >>> from zope.configuration.config import ConfigurationMachine >>> from collective.generic.webbuilder.zcml import PasterConfiguration, Template, Group, ExcludeOption, Option >>> from collective.generic.webbuilder.models import root >>> from minitage.paste.projects import plone3 >>> import collective.generic.webbuilder >>> context = ConfigurationMachine() >>> xmlconfig.registerCommonDirectives(context) >>> xmlconfig.include(context, 'meta.zcml', collective.generic.webbuilder) >>> context = xmlconfig.string(""" ... <configure xmlns="http://webbuilder.org/webbuilder"> ... <genericpaster name="Test Generic Portal Plone"> ... <!--<plugin name="dummy_plugin" order="1"/>--> ... <plugin name="egg_plugin" order="2"/> ... <template name="collective.generic.policy" output="src" order="200"> ... <excludeoptions prefix="project_.*" /> ... <excludeoption name="python" /> ... </template> ... <template name="minitage.plone3" order="1"> ... <group name="Minitage" order="05"> ... <option name="install_method" alias="ai"/> ... <options prefix=".*with.*" default="true" type="boolean"/> ... <excludeoptions prefix="project_.*" /> ... <excludeoption name="python" /> ... </group> ... </template> ... </genericpaster> ... </configure> ... """, context = context)
它将注册/更新 collective.generic.webbuilder.root.configurations 模块变量
genericpaster指令
必须在最顶层使用。
模板配置的名称。
<genericpaster name="Name of the configuration"/>
它包含一组底层配置
>>> 'Test Generic Portal Plone' in root.configurations True
配置对象包含模板和插件列表
>>> templates = root.configurations['Test Generic Portal Plone'].templates >>> sorted(templates.keys()) ['collective.generic.policy', 'minitage.plone3']
template指令
必须在 genericpaster 级别使用。
它描述了一个相对的“paster模板”。你可以通过 paster create -t --list-templates 获取这个名称。
它还有一个顺序,用于在web界面中按从低到高的顺序排列模板。
<template name="Template Name" order="int">
>>> t = templates['minitage.plone3'] >>> t.order 1 >>> t.name 'minitage.plone3'
模板还可以通过 output 属性指定必须在“子目录”下生成。
>>> templates['collective.generic.policy'].output 'src'
group指令
模板有一个选项组列表。
组在web界面中由一个围绕组名的问题块表示。
<group name="GroupName" order="int"/>
这些组将“paster问题”分组。
>>> groups = t.groups >>> groups.keys() ['default', 'Minitage'] >>> g = t.groups['Minitage'] >>> t.groups['Minitage'].order 5 >>> t.groups['Minitage'].name 'Minitage'
options指令
必须在组级别使用。
组分组选项,可以通过此指令使用正则表达式获取。
<options prefix="Regular expression" type="boolean|" default="value"/>
type 可以省略,默认为 None (文本)。
default 可以省略,不会分配默认值(或 paster 默认值)。
>>> opts = g.options['.*with.*'] >>> opts.type, opts.default ('boolean', 'true')
如你所见,有一个默认组,其中包含未通过 excludeoptions 指令排除的非匹配选项。
option指令
必须在组级别使用。
组还分组“单个选项”,可以通过其名称获取。
单个选项可以有别名。如果有两个模板中的相同“选项名称”我们不想它们共享相同的值(默认行为),这很有用。明确来说,我们有在模板“a”和“b”中的“project”选项,默认情况下,如果我们选择“project”为“foo”,则在模板“a”和“b”中的值将是“foo”,而使用别名,我们可以为“a”和“b”选择不同的值。
<options name="name" alias="alias name" type="boolean|" default="value"/>
alias 可以省略。
type 可以省略,默认为 None (文本)。
default 可以省略,不会分配默认值(或 paster 默认值)。
>>> opt = g.single_options['install_method'] >>> opt.type, opt.alias, opt.default (None, 'ai', None)
excludeoptions & excludeoption指令
必须在组或模板级别使用(在模板的任何组中)。
<excludeoptions prefix="regular expression"/>
从界面中排除选项
prefix:要排除的选项的正则表达式。
<excludeoption name="option name"/>
从界面中排除选项
name:要排除的选项的名称。
>>> [[getattr(templates[template].groups['default'], attr).keys() for attr in 'exclude_options', 'excludes_options'] for template in 'minitage.plone3', 'collective.generic.policy'] [[['python'], ['project_.*']], [['python'], ['project_.*']]]
plugin指令
必须在模板级别使用。
声明在模板集合生成后必须运行的插件。
例如,重新排列生成的内容很有用。
运行在“插件名称”下声明的插件。
<plugin name="plugin name" order="int"/>
name:适配器的名称
order:如果有多个插件,则控制运行顺序
插件是一个简单的适配器,它接受一个 IPasterAssembly 并提供 IPostGenerationPlugin
<adapter name="plugin name" provides=".interfaces.IPostGenerationPlugin" factory=".plugins.MyPluginFactory" for=".interfaces.IPasterAssembly" />
>>> plugins = root.configurations['Test Generic Portal Plone'].plugins >>> plugins [('egg_plugin', 2)]
paster舞步
cgwb 的核心是 pythonpaste,它取一些 paster 模板,将它们收集到一个 ihm 中,供用户输入和回答,以生成这些模板的最终组合,这些模板可能已经被周围的插件修改过。
User choose a configuration ---------> read variables from templates which are in the configuration and give the appropriate choice to the user -------------> User inputs and submit it --------------------> We generate a tarball of the assembled templates according to the answers
一个选项只问一次,只有为具有相同名称的每个选项创建别名。
由于一个问题只问一次,如果其类型不是默认类型,你必须在其顺序编号最小的模板配置中定义它,因为那里将询问该问题。
加载配置的zcml表示
测试zcml到python的表示
加载我们的测试包,其中包含三个模板
>>> import collective.generic.webbuilder.tests >>> testegg = os.path.join( collective.generic.webbuilder.tests.__path__[0], 'egg', 'src') >>> pkg_resources.working_set.add_entry(testegg) >>> env = pkg_resources.Environment() >>> egg = env['cgwb.tp'][0]
这里有3个模板等待组合
>>> pprint(egg.get_entry_map()) {'paste.paster_create_template': {'cgwb.testpackage1': EntryPoint.parse('cgwb.testpackage1 = tp.package:Package'), 'cgwb.testpackage2': EntryPoint.parse('cgwb.testpackage2 = tp1.package:Package'), 'cgwb.testpackage3': EntryPoint.parse('cgwb.testpackage3 = tp2.package:Package')}}
配置
在文档的zcml部分有更详细的描述,但它代表了从pastertemplates中要提取哪些变量以及如何将它们呈现给用户。
以下是需要组装我们之前声明的包的示例zcml:
>>> paster_zcml = """ ... <configure xmlns="http://namespaces.repoze.org/bfg" xmlns:meta="http://namespaces.zope.org/meta"> ... <include package="collective.generic.webbuilder" file="meta.zcml"/> ... <configure xmlns="http://webbuilder.org/webbuilder"> ... <genericpaster name="test Assembler"> ... <template name="cgwb.testpackage1" output="1" order="1000"> ... <group name="Minitage" order="05"> ... <option name="tp1option" type="boolean"/> ... <option name="tp1option3" default="y"/> ... </group> ... </template> ... <template name="cgwb.testpackage2" output="2" order="200"> ... <group name="Authors" order="20"> ... <options prefix="^author.*"/> ... </group> ... <excludeoptions prefix=".*"/> ... </template> ... <template name="cgwb.testpackage3" output="3" order="500"> ... <group name="Plone Settings" order="8"> ... <option name="tp2opton2" /> ... <option name="author_email" /> ... </group> ... <group name="Authors" order="20"> ... <options prefix="^author.*"/> ... </group> ... <group name="Package tuning" order="1"> ... <option name="project_name" type="hidden" default="tma" alias="tmapn"/> ... </group> ... </template> ... </genericpaster> ... </configure> ... </configure> ... """ >>> noecho = xmlconfig.string(paster_zcml) >>> root.configurations['test Assembler'] <collective.generic.webbuilder.zcml.PasterConfiguration object at ...>
PasterAssembly对象
有一些“日志变量”以及要在“bfgroot.configurations”字典中搜索的配置名称。
template_data:列表映射的形式
[ { 'self': template 'zcml' object, 'name': paster template name, 'added_options': option added by this template, 'not_explicit_options': option added by this template which were not explicitly matched, 'display' : display a template or not 'groups': { groupname: { 'name': groupname, 'group': zcml group object: 'options': [(paster variablen, type, optionn name, alias|None, zcml optionn|None )] } }, 'aliases': [ (varName, aliasName),] } ]added_options:为所有模板添加的所有选项
获取所需配置的组装
>>> ta = gpaster.PasterAssembly('test Assembler') >>> pprint(ta.__dict__.items()) [('templates_data', []), ('configuration', <collective.generic.webbuilder.zcml.PasterConfiguration object at ...>), ('added_options', []), ('configuration_name', 'test Assembler')]
PasterAssemblyReader对象
此适配器接受一个IPasterAssembly对象,并实现了IPasterAssemblyReader接口。
我们已经将配置存储到zcml表示中,现在我们需要收集并映射配置信息与每个“paster模板”的内容到一个Python友好的结构中。此Reader组件负责存储在组装对象中
提取的模板名称
每个选项组
对于这些组中的每一个
排除的选项
对于未排除的选项,如果适用的话
创建别名
分配默认值
最终将加载组装数据结构的是一个现在知道如何解析配置的读取器
>>> reader = gpaster.PasterAssemblyReader(ta) >>> reader.readed False >>> reader.read() >>> len(ta.added_options) > 0 True >>> reader.readed True
我们现在将检查加载的结构是否符合我们的预期
>>> t1 = pkg_resources.load_entry_point('cgwb.tp', 'paste.paster_create_template', 'cgwb.testpackage1') >>> t2 = pkg_resources.load_entry_point('cgwb.tp', 'paste.paster_create_template', 'cgwb.testpackage2') >>> t3 = pkg_resources.load_entry_point('cgwb.tp', 'paste.paster_create_template', 'cgwb.testpackage3')
模板
模板的顺序被尊重
>>> [t['name'] for t in ta.templates_data] ['cgwb.testpackage2', 'cgwb.testpackage3', 'cgwb.testpackage1'] >>> rt2, rt3, rt1 = ta.templates_data
组
模板3在paster端上的选项,我们将在下一个示例中找到
>>> pprint([v.name for v in t3.vars]) ['namespace', 'nested_namespace', 'version', 'author', 'author_email', 'tp3option', 'tp3option3', 'keywords', 'license_name', 'project_name']
对于每个模板,选项被分组,并且组遵循zcml中定义的顺序
>>> pprint([(rt3['groups'][n]['name'] , rt3['groups'][n]['options']) for n in range(len(rt3['groups']))]) [('Package tuning', [(<var project_name default='tma' should_echo=True>, 'hidden', 'tmapn', <collective.generic.webbuilder.zcml.Option object at ...>)]), ('Plone Settings', [(<var author_email default='bar@localhost' should_echo=True>, 'default', None, <collective.generic.webbuilder.zcml.Option object at ...>)]), ('Authors', [(<var author default='foo' should_echo=True>, 'default', None, <collective.generic.webbuilder.zcml.Options object at ...>)]), ('default', [(<var namespace default='%(namespace)s' should_echo=True>, 'default', None, None), (<var nested_namespace default='%(package)s' should_echo=True>, 'default', None, None), (<var version default='1.0' should_echo=True>, 'default', None, None), (<var tp3option default='https://pythonlang.cn' should_echo=True>, 'default', None, None), (<var tp3option3 default='Project %s' should_echo=True>, 'default', None, None), (<var keywords default='' should_echo=True>, 'default', None, None), (<var license_name default='GPL' should_echo=True>, 'default', None, None)])]
如您所见,project_name已被别名化,将在后面解释。
消耗选项
目标是强烈强调已消耗的选项。当一个选项被另一个模板消耗时,它对其他选项不可用,只能询问一次。这就是为什么,模板1没有在模板3中首次询问的变量。
>>> rt3options = []; noecho = [rt3options.extend(g['options']) for g in rt3['groups']]; rt3options = [opt[0].name for opt in rt3options] >>> rtp1options = []; noecho = [rtp1options.extend(g['options']) for g in rt1['groups']]; rtp1options = [opt[0].name for opt in rtp1options]
project_name、author等不是模板1选项的一部分,即使它们在paster模板中。它们已经被模板3消耗了
>>> pprint([v.name for v in t1.vars]) ['namespace', 'nested_namespace', 'version', 'author', 'author_email', 'tp1option', 'tp1option2', 'tp1option3', 'keywords', 'license_name', 'project_name'] >>> rt3options ['project_name', 'author_email', 'author', 'namespace', 'nested_namespace', 'version', 'tp3option', 'tp3option3', 'keywords', 'license_name'] >>> rtp1options ['tp1option', 'tp1option3', 'tp1option2', 'project_name']
排除选项
模板2默认忽略所有选项,即使添加一个选项也不能优先于忽略选项。注意你的正则表达式!
>>> [g['options'] for g in rt2['groups']] [[], []]
类型选项
我们可以为值分配类型,以使用不同的小部件在UI中显示它们,例如。支持的类型有
布尔值(复选框)
隐藏(隐藏)
默认(文本区域)
模板3将project_name定义为hidden
>>> rt3['groups'][0]['options'][0][1] 'hidden'
模板1将tp1option定义为boolean。
>>> rt1['groups'][0]['options'][0][1] 'boolean'
如果选项的默认值以“y”、“true”或“on”开头,我们将选项类型切换为布尔值
>>> rt1['groups'][0]['options'][1][1] 'boolean'
默认组中的选项具有default类型,就像没有显式类型的选项一样
>>> rt3["groups"][3]['options'][0][1] 'default' >>> rt3["groups"][2]['options'][0][1] 'default'
选项别名
我们已为template3.project_name定义了一个默认值和默认类型,它也是一个别名。别名允许在同一个组装中存在具有相同名称但不同值的选项。默认行为表示一个值只询问一次,并用于具有相同名称的所有选项,除非它们被显式地分别别名为每个选项。
>>> rt3['groups'][0]['options'] [(<var project_name default='tma' should_echo=True>, 'hidden', 'tmapn', <collective.generic.webbuilder.zcml.Option object at ...>)]
添加选项
我们可以检索添加的选项
对于模板
>>> rt1['added_options'] ['tp1option', 'tp1option2', 'tp1option3', 'project_name'] >>> rt2['added_options'] [] >>> rt3['added_options'] ['namespace', 'nested_namespace', 'version', 'author', 'author_email', 'tp3option', 'tp3option3', 'keywords', 'license_name', 'project_name']对于所有模板
>>> ta.added_options ['namespace', 'nested_namespace', 'version', 'author', 'author_email', 'tp3option', 'tp3option3', 'keywords', 'license_name', 'tmapn', 'tp1option', 'tp1option2', 'tp1option3', 'project_name']
创建插件以在成功生成模板后重新排列事物
插件是一个简单的适配器
创建在生成后运行的插件非常简单。只需实现一个适配器,该适配器接受一个\IPasterConfiguration并提供IPostGenerationPlugin。
<adapter name="plugin name" provides=".interfaces.IPostGenerationPlugin" factory=".plugins.MyPluginFactory" for=".interfaces.IPasterAssembly" />
eggs插件
例如,这里有一个简单的插件,它将“src”目录中的所有egg注册到相对的“buildout.cfg”中的“zcml”和“develop”。它将只将“policy”egg添加到实例的zcml选项中。
模拟生成的样板
>>> import tempfile, shutil, os >>> c = os.getcwd() >>> d = tempfile.mkdtemp() >>> os.chdir(d) >>> open('buildout.cfg', 'w').write('[buildout]\ndevelop+=\n foo\n[instance]\nzcml= too\n') >>> os.makedirs('src/bar/src') >>> os.makedirs('src/policy/src') >>> open('src/bar/setup.py', 'w').write('') >>> open('src/policy/setup.py', 'w').write('')
运行插件
>>> from collective.generic.webbuilder.models import root >>> conf = root.configurations['Generic Portal Plone3'] >>> from collective.generic.webbuilder import interfaces, paster >>> pa = paster.PasterAssembly('Generic Portal Plone3') >>> plugin = zope.component.queryAdapter(pa, interfaces.IPostGenerationPlugin, name='egg_plugin') >>> plugin.process(d, 'foo', {}) >>> print open('buildout.cfg').read() [buildout] develop+=src/policy src/bar foo eggs += policy bar [instance] zcml= too policy <BLANKLINE>
清理
>>> os.chdir(c);shutil.rmtree(d)
注册插件
请参阅plugin zcml指令,了解如何为“Configuration”添加插件。
以下是一个关于“eggs_plugins”的示例
<configure xmlns="http://namespaces.repoze.org/bfg" xmlns:meta="http://namespaces.zope.org/meta" xmlns:cgwb=xmlns="http://webbuilder.org/webbuilder"> <adapter name="egg_plugin" provides=".interfaces.IPostGenerationPlugin" factory=".plugins.EggPlugin" for=".interfaces.IPasterAssembly" /> <cgwb:genericpaster name="Generic Portal Plone"> <cgwb:plugin name="egg_plugin" order="2"/> <cgwb:template name="minitage.plone3" order="1"> </template> </genericpaster> </configure>
paster舞步
cgwb 的核心是 pythonpaste,它取一些 paster 模板,将它们收集到一个 ihm 中,供用户输入和回答,以生成这些模板的最终组合,这些模板可能已经被周围的插件修改过。
User choose a configuration ---------> read variables from templates which are in the configuration and give the appropriate choice to the user -------------> User inputs and submit it --------------------> We generate a tarball of the assembled templates according to the answers
一个选项只问一次,只有为具有相同名称的每个选项创建别名。
由于一个问题只问一次,如果其类型不是默认类型,你必须在其顺序编号最小的模板配置中定义它,因为那里将询问该问题。
加载配置的zcml表示
测试zcml到python的表示
加载我们的测试包,其中包含三个模板
>>> import collective.generic.webbuilder.tests >>> testegg = os.path.join( collective.generic.webbuilder.tests.__path__[0], 'egg', 'src') >>> pkg_resources.working_set.add_entry(testegg) >>> env = pkg_resources.Environment() >>> egg = env['cgwb.tp'][0]
配置
在文档的zcml部分有更详细的描述,但它代表了从pastertemplates中要提取哪些变量以及如何将它们呈现给用户。
以下是需要组装我们之前声明的包的示例zcml:
>>> paster_zcml = """ ... <configure xmlns="http://namespaces.repoze.org/bfg" xmlns:meta="http://namespaces.zope.org/meta"> ... <include package="collective.generic.webbuilder" file="meta.zcml"/> ... <configure xmlns="http://webbuilder.org/webbuilder"> ... <genericpaster name="test Assembler"> ... <template name="cgwb.testpackage1" output="1" order="1000"> ... <group name="Minitage" order="05"> ... <option name="tp1option" type="boolean"/> ... <option name="tp1option3" default="y"/> ... </group> ... </template> ... <template name="cgwb.testpackage2" output="2" order="200"> ... <group name="Authors" order="20"> ... <options prefix="^author.*"/> ... </group> ... <excludeoptions prefix=".*"/> ... </template> ... <template name="cgwb.testpackage3" output="3" order="500"> ... <group name="Plone Settings" order="8"> ... <option name="tp2opton2" /> ... <option name="author_email" /> ... </group> ... <group name="Authors" order="20"> ... <options prefix="^author.*"/> ... </group> ... <group name="Package tuning" order="1"> ... <option name="project_name" type="hidden" default="tma" alias="tmapn"/> ... </group> ... </template> ... </genericpaster> ... </configure> ... </configure> ... """ >>> noecho = xmlconfig.string(paster_zcml) >>> root.configurations['test Assembler'] <collective.generic.webbuilder.zcml.PasterConfiguration object at ...>
我们现在将检查加载的结构是否符合我们的预期
>>> t1 = pkg_resources.load_entry_point('cgwb.tp', 'paste.paster_create_template', 'cgwb.testpackage1') >>> t2 = pkg_resources.load_entry_point('cgwb.tp', 'paste.paster_create_template', 'cgwb.testpackage2') >>> t3 = pkg_resources.load_entry_point('cgwb.tp', 'paste.paster_create_template', 'cgwb.testpackage3') >>> server, url = launch_server() >>> browser = Browser(url)
我们可以看到在主页上,我们有默认配置和自定义加载的配置
>>> 'test Assembler' in browser.contents True >>> 'Generic Portal Plone4' in browser.contents True >>> 'Generic Portal Plone3' in browser.contents True
点击测试组装链接
>>> browser.getLink('test Assembler').click() >>> htmlS(browser.contents).xpath('//input[@name="project"]')[0] <InputElement ... name='project' type='text'>
我可以提交一个表单并验证它
>>> browser.getControl(name='project').value = 'myproject' >>> browser.getControl(name='author').value = 'tim burton' >>> browser.getControl(name='author_email').value = 'tim burton@foo.com' >>> browser.getControl(name='tp1option').value = False >>> browser.getControl(name='tp1option2').value = 'Project Monster' >>> browser.getControl(name='project_name').value = 'My Big Project' >>> browser.getControl(name='submit_cgwbDownload').click() >>> '.tar' in browser.contents True
成功生成的结果是一个tar包
>>> pprint(browser.headers.headers) ['Server:... 'Date:... 'Content-Disposition: attachment; filename="myproject....tar.gz"\r\n', 'Content-Transfer-Encoding: binary\r\n', 'Content-Length: ...\r\n'] >>> import tarfile >>> tar = tarfile.open(fileobj=StringIO(browser.contents))
在生成的tarball中,尊重zcml配置中存在的输出目录
>>> files = [a.name for a in tar];files.sort();pprint(files) ['.', '1', '1/myproject', '1/myproject/test', '2', '2/myproject', '2/myproject/test1', '3', '3/myproject', '3/myproject/test2'] >>> templates = dict([(a.name,a) for a in tar if 'test' in a.name]) >>> t2 = templates['2/myproject/test1'] >>> t3 = templates['3/myproject/test2'] >>> t1 = templates['1/myproject/test']
在界面中填写的选项在模板中得到了良好解释
>>> pprint([a for a in tar.extractfile(t1).read().split('\n') if a.strip()]) ['namespace => %(namespace)s', 'nested_namespace => %(package)s', 'version => 1.0', 'author => tim burton', 'author_email => tim burton@foo.com', 'tp1option => False', 'tp1option2 => Project Monster', 'tp1option3 => True', 'keywords => ', 'license_name => GPL', 'project_name => My Big Project']
为project1输入的项目名称在project2中被共享
>>> pprint([a for a in tar.extractfile(t2).read().split('\n') if a.strip()]) ["'namespace' => '%(namespace)s'", "'nested_namespace' => '%(package)s'", "'version' => '1.0'", "'author' => 'tim burton'", "'author_email' => 'tim burton@foo.com'", "'keywords' => ''", "'license_name' => 'GPL'", "'project_name' => 'My Big Project'", "'tp2option' => 'tp2option'", "'tp2opton2' => 'tp2opton2'"]
别名项目名称(tma)在第三个模板中生效
>>> pprint([a for a in tar.extractfile(t3).read().split('\n') if a.strip()]) ["'namespace' => '%(namespace)s'", "'nested_namespace' => '%(package)s'", "'version' => '1.0'", "'author' => 'tim burton'", "'author_email' => 'tim burton@foo.com'", "'keywords' => ''", "'license_name' => 'GPL'", "'project_name' => 'tma'", "'tp3option3' => 'Project %s'", "'tp3option' => 'Project %s'"]
变更日志
1.1
文档,因为webbuilder需要在开发模式下安装,无论如何。
1.0
初始版本
项目详情
下载文件
下载适用于您平台的文件。如果您不确定选择哪个,请了解有关安装包的更多信息。
源分发
collective.generic.webbuilder-1.1.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 4ea38f499d564950fe98b8ae194fa6019f695d00adb66d9dc51cf2fa9480b04f |
|
MD5 | bfa3b0506e3af818e313940f5bd0396e |
|
BLAKE2b-256 | bce434eb88dc9ada4d2c7f8be3adcc283be2aecf24626efdc82494883d0a0226 |