(第9回)Python mini Hack-a-thon

こちら(http://atnd.org/events/15877)に参加してきました。

Ploneの新しいContent Type FrameworkのDexterityを触りました。前回は去年の一月末にいじったままでしたが、2011/05/20に1.0がリリースされたので良い機会ということで再開しました。

今回の目的はファイルベースでの開発を理解することです。
あと、開発は簡単にできるようにUnifiedInstallerから順次進めていきます。

参考にするドキュメントは前回と同じこれ(http://plone.org/products/dexterity/documentation/manual/developer-manual)です。

1.Ploneのインストール

今日現在のバージョンは4.0.7です。
Plone-4.0.7-UnifiedInstallerをここ(http://plone.org/products/plone)からダウンロードしてインストールします。

  # tar xvfz Plone-4.0.7-UnifiedInstaller.tgz
  # cd Plone-4.0.7-UnifiedInstaller
  # ./install.sh standalone

続いてPlone実行します。
PloneのディレクトリはFreeBSD/Linuxだと/usr/local/Ploneで、Mac OS Xだと/Applications/Ploneです。

  # cd /Applications/Plone
  # cd zinstance
  # bin/instance fg

http://localhost:8080/にアクセスしてPloneサイトを作成します。

Ploneサイトを作成して、http://localhost:8080/Ploneにアクセスします。

2. DexterityをTTW(Through The Web)で使えるようにする。

buildout.cfgを下記のように書き換えます。

extends =
    base.cfg
#    versions.cfg                                                               
     http://good-py.appspot.com/release/dexterity/1.0?plone=4.0.7
#    http://dist.plone.org/release/4.0-latest/versions.cfg                      
versions = versions
develop =
eggs =
    Plone
    Pillow
    plone.app.dexterity

このままbin/buildoutするとdistributeのバージョンがコンフリクトする(!=0.6.16)と言われるのでbootstrap.pyを実行し直します。

  # ../Python-2.6/bin/python bootstrap.py
  # bin/buildout

Ploneを立ち上げて、サイト設定(http://localhost:8080/Plone/plone_control_panel)に行って、アドオン(http://localhost:8080/Plone/prefs_install_products_form)へ行って、「Dexterity Content Types 1.0」というのがあればOKです。

チェックボックをチェックして、「有効にする」を押せば、DexterityがTTW(Through The Web)で使えるようになります。

4. Dexterityをファイルから使えるようにする

一からパッケージを作成する方法をDeveloper Manualの通りに進めていきます。
元のexample.conferenceはzinstance/srcにあるので、別名にしておきます。

zinstance/srcでpasterを実行します。

  # ../bin/paster create -t plone example.conference

buildout.cfgを下記のように書き換えます。

auto-checkout = をコメントアウト。

#auto-checkout =                                                                
#    example.conference  

example.conferenceのソースの場所を明示。

develop =
    src/example.conference
#    src/my.package

このままでは、利用できるアドオンにexample.conferenceは出てきません。

順次、使えるようにしていきます。

4.1 packageのための最小限のファイル

ここ(http://plone.org/products/dexterity/documentation/manual/developer-manual/pre-requisites/creating-a-package)の通りにファイルを追加します。

example.conference/setup.py

from setuptools import setup, find_packages
import os

version = '1.0a1'

setup(name='example.conference',
      version=version,
      description="Example accompanying http://plone.org/products/dexterity/documentation/manual/developers-manual/",
      long_description=open("README.txt").read() + "\n" +
                       open(os.path.join("docs", "HISTORY.txt")).read(),
      # Get more strings from http://www.python.org/pypi?%3Aaction=list_classifiers
      classifiers=[
        "Framework :: Plone",
        "Programming Language :: Python",
        "Topic :: Software Development :: Libraries :: Python Modules",
        ],
      keywords='plone dexterity example',
      author='Martin Aspeli',
      author_email='optilude@gmail.com',
      url='http://plone.org/products/dexterity',
      license='GPL',
      packages=find_packages(exclude=['ez_setup']),
      namespace_packages=['example'],
      include_package_data=True,
      zip_safe=False,
      install_requires=[
          'setuptools',
          'Plone',
          'plone.app.dexterity',
          'collective.autopermission',
      ],
      entry_points="""
      [z3c.autoinclude.plugin]
      target = plone
      """,
      )

example.conference/example/conference/configure.zcml

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:grok="http://namespaces.zope.org/grok"
    xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
    i18n_domain="example.conference">

    <!-- Include configuration for dependencies listed in setup.py -->
    <includeDependencies package="." />

    <!-- Grok the package to initialise schema interfaces and content classes -->
    <grok:grok package="." />

    <!-- Register an extension profile to make the product installable -->
    <genericsetup:registerProfile
        name="default"
        title="Conference management"
        description="A Dexterity demo"
        directory="profiles/default"
        provides="Products.GenericSetup.interfaces.EXTENSION"
        />
        
</configure>

example.conference/example/conference/profiles/defaultというディレクトリを作成して、

example.conference/example/conference/profiles/default/metadata.xml

<metadata>
    <version>1</version>
    <dependencies>
        <dependency>profile-plone.app.dexterity:default</dependency>
    </dependencies>
</metadata>

bin/buildoutすると、利用できるアドオンに「Conference management 1.0a1」が見えるようになりました。実際にはconfigure.zcmlだけ書き換えれば見えるようになります。

これをチェックして有効にするにしても、何もコンテンツは定義していないので、何も変わりません。

4.2 SchemaとFTI

Schemaを書きます。

example.conference/example/conference/__init__.py

from zope.i18nmessageid import MessageFactory

_ = MessageFactory("example.conference")

example.conference/example/conference/presenter.py

from five import grok
from zope import schema

from plone.directives import form, dexterity

from plone.app.textfield import RichText
from plone.namedfile.field import NamedImage

from example.conference import _

class IPresenter(form.Schema):
    """A conference presenter. Presenters can be added anywhere.
    """
    
    title = schema.TextLine(
            title=_(u"Name"),
        )
    
    description = schema.Text(
            title=_(u"A short summary"),
        )
    
    bio = RichText(
            title=_(u"Bio"),
            required=False
        )
    
    picture = NamedImage(
            title=_(u"Picture"),
            description=_(u"Please upload an image"),
            required=False,
        )

example.conference/example/conference/program.py

from five import grok
from zope import schema

from plone.directives import form, dexterity
from plone.app.textfield import RichText

from example.conference import _

class IProgram(form.Schema):
    """A conference program. Programs can contain Sessions.
    """
    
    title = schema.TextLine(
            title=_(u"Program name"),
        )
    
    description = schema.Text(
            title=_(u"Program summary"),
        )
    
    start = schema.Datetime(
            title=_(u"Start date"),
            required=False,
        )

    end = schema.Datetime(
            title=_(u"End date"),
            required=False,
        )
    
    details = RichText(
            title=_(u"Details"),
            description=_(u"Details about the program"),
            required=False,
        )

example.conference/example/conference/session.py
サンプルはMessageFactoryのインポートが抜けているので書き足します。

from five import grok
from zope import schema

from plone.directives import form, dexterity
from plone.app.textfield import RichText

from example.conference import _

class ISession(form.Schema):
    """A conference session. Sessions are managed inside Programs.                              
    """

    title = schema.TextLine(
            title=_(u"Title"),
            description=_(u"Session title"),
        )

    description = schema.Text(
            title=_(u"Session summary"),
        )

    details = RichText(
            title=_(u"Session details"),
            required=False
        )

FTI(Factory Type Information)を書きます。

example.conference/example/conference/profiles/default/types.xml

<object name="portal_types">
 <object name="example.conference.presenter" meta_type="Dexterity FTI" />
 <object name="example.conference.program" meta_type="Dexterity FTI" />
 <object name="example.conference.session" meta_type="Dexterity FTI" />
</object>

example.conference/example/conference/profiles/default/typesというディレクトリを作成して、ファイルを追加します。

example.conference/example/conference/profiles/default/types/example.conference.presenter.xml

<?xml version="1.0"?>
<object name="example.conference.presenter" meta_type="Dexterity FTI"
   i18n:domain="example.conference" xmlns:i18n="http://xml.zope.org/namespaces/i18n">
 
 <!-- Basic metadata -->
 <property name="title" i18n:translate="">Presenter</property>
 <property name="description" i18n:translate="">A person presenting sessions</property>
 <property name="content_icon">user.gif</property>
 <property name="allow_discussion">True</property>
 <property name="global_allow">True</property>
 <property name="filter_content_types">True</property>
 <property name="allowed_content_types" />
 
 <!-- schema interface -->
 <property name="schema">example.conference.presenter.IPresenter</property> 
 
 <!-- class used for content items -->
 <property name="klass">plone.dexterity.content.Item</property>
 
 <!-- add permission -->
 <property name="add_permission">cmf.AddPortalContent</property>
 
 <!-- enabled behaviors -->
 <property name="behaviors">
     <element value="plone.app.content.interfaces.INameFromTitle" />
 </property>
 
 <!-- View information -->
 <property name="default_view">view</property>
 <property name="default_view_fallback">False</property>
 <property name="view_methods">
  <element value="view"/>
 </property>
 
 <!-- Method aliases -->
 <alias from="(Default)" to="(dynamic view)"/>
 <alias from="edit" to="@@edit"/>
 <alias from="sharing" to="@@sharing"/>
 <alias from="view" to="(selected layout)"/>
 
 <!-- Actions -->
 <action title="View" action_id="view" category="object" condition_expr=""
    url_expr="string:${object_url}" visible="True">
  <permission value="View"/>
 </action>
 <action title="Edit" action_id="edit" category="object" condition_expr=""
    url_expr="string:${object_url}/edit" visible="True">
  <permission value="Modify portal content"/>
 </action>
</object>

example.conference/example/conference/profiles/default/types/example.conference.session.xml

<?xml version="1.0"?>
<object name="example.conference.session" meta_type="Dexterity FTI"
   i18n:domain="example.conference" xmlns:i18n="http://xml.zope.org/namespaces/i18n">
 
 <!-- Basic metadata -->
 <property name="title" i18n:translate="">Session</property>
 <property name="description" i18n:translate="">A session on the program</property>
 <property name="content_icon">document_icon.gif</property>
 <property name="allow_discussion">True</property>
 <property name="global_allow">False</property>
 <property name="filter_content_types">True</property>
 <property name="allowed_content_types" />
 
 <!-- schema interface -->
 <property name="schema">example.conference.session.ISession</property> 
 
 <!-- class used for content items -->
 <property name="klass">plone.dexterity.content.Item</property>
 
 <!-- add permission -->
 <property name="add_permission">cmf.AddPortalContent</property>
 
 <!-- enabled behaviors -->
 <property name="behaviors">
     <element value="plone.app.content.interfaces.INameFromTitle" />
 </property>
 
 <!-- View information -->
 <property name="default_view">view</property>
 <property name="default_view_fallback">False</property>
 <property name="view_methods">
  <element value="view"/>
 </property>
 
 <!-- Method aliases -->
 <alias from="(Default)" to="(dynamic view)"/>
 <alias from="edit" to="@@edit"/>
 <alias from="sharing" to="@@sharing"/>
 <alias from="view" to="(selected layout)"/>
 
 <!-- Actions -->
 <action title="View" action_id="view" category="object" condition_expr=""
    url_expr="string:${object_url}" visible="True">
  <permission value="View"/>
 </action>
 <action title="Edit" action_id="edit" category="object" condition_expr=""
    url_expr="string:${object_url}/edit" visible="True">
  <permission value="Modify portal content"/>
 </action>
</object>

example.conference/example/conference/profiles/default/types/example.conference.program.xml

<?xml version="1.0"?>
<object name="example.conference.program" meta_type="Dexterity FTI"
   i18n:domain="example.conference" xmlns:i18n="http://xml.zope.org/namespaces/i18n">
 
 <!-- Basic metadata -->
 <property name="title" i18n:translate="">Program</property>
 <property name="description" i18n:translate="">A conference program</property>
 <property name="content_icon">folder_icon.gif</property>
 <property name="allow_discussion">True</property>
 <property name="global_allow">True</property>
 <property name="filter_content_types">True</property>
 <property name="allowed_content_types">
     <element value="example.conference.session" />
 </property>
 
 <!-- schema interface -->
 <property name="schema">example.conference.program.IProgram</property> 
 
 <!-- class used for content items -->
 <property name="klass">plone.dexterity.content.Container</property>
 
 <!-- add permission -->
 <property name="add_permission">cmf.AddPortalContent</property>
 
 <!-- enabled behaviors -->
 <property name="behaviors">
     <element value="plone.app.content.interfaces.INameFromTitle" />
 </property>
 
 <!-- View information -->
 <property name="default_view">view</property>
 <property name="default_view_fallback">False</property>
 <property name="view_methods">
  <element value="view"/>
 </property>
 
 <!-- Method aliases -->
 <alias from="(Default)" to="(dynamic view)"/>
 <alias from="edit" to="@@edit"/>
 <alias from="sharing" to="@@sharing"/>
 <alias from="view" to="(selected layout)"/>
 
 <!-- Actions -->
 <action title="View" action_id="view" category="object" condition_expr=""
    url_expr="string:${object_url}" visible="True">
  <permission value="View"/>
 </action>
 <action title="Edit" action_id="edit" category="object" condition_expr=""
    url_expr="string:${object_url}/edit" visible="True">
  <permission value="Modify portal content"/>
 </action>
</object>

bin/buildoutして利用できるアドオンにします。

とりあえず、使えるようになりました。

ちなみに、TTWでファイルから作成したContent Typeを編集できるようになっています。

次回のハッカソンでも続けていきます。

ハッカソン後の懇親会で話をしているうちに、PyQtをやるべきだという気になってきました。
あとは、Common Lisp再開です。

3. Dexterityをファイルから使えるようにする(前準備)

Dexterity Developer Manualのサンプルのexample.conferenceの完成品を持ってきて、まず使えるか確かめてみます。

buildout.cfgを下記のように書き換えます。

extensions = mr.developer buildout.dumppickedversions
extends =
    base.cfg
#    versions.cfg                                                               
     http://good-py.appspot.com/release/dexterity/1.0?plone=4.0.7
#    http://dist.plone.org/release/4.0-latest/versions.cfg                      
versions = versions
develop =
sources = sources
auto-checkout =
    example.conference
eggs =
    Plone
    Pillow
    plone.reload
    plone.app.dexterity
    example.conference
[sources]
example.conference = svn https://svn.plone.org/svn/collective/example.conference/trunk

利用できるアドオンに「Conference management 1.0a1dev-r239922」があればOKです。

Developer Manualには無い、attendeeというコンテンツが増えています。