跳转到主要内容

又一个由Makina Corpus编写的WSGI Paste工厂

项目描述

CGWB是paster的Web界面,其目标是生成一个从一组模板聚合的选项的Web界面。

想象一下,您有两个模板,一个可以部署应用程序,另一个可以生成应用程序本身。

将这两个模板声明为 cgwb 集合 将为这两个模板创建一个 web 接口。正确回答问题将生成一个 tarball,您可以下载并解压以设置基本安装。

要使模板可用,您必须使用 ZCML 定义该集合。

由于该服务器被开发为 paster 的快速和高效接口,不建议将其暴露在互联网上。 为了安全起见,仅在需要时启动/使用。

下一个版本将包括一些会话/角色和改进的安全功能,这个阶段可能可以将其保持开放。

请在此处查看实际效果 here

致谢

公司

makinacom

作者

安装

在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

填写设置,一些说明

  • 项目名称是必填的,并且必须采用 projectsubproject 的形式。

  • 您可以在 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 (233.6 kB 查看哈希值)

上传时间

由以下支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误记录 StatusPage StatusPage 状态页