Archetypesを使った開発(一部略)
The Definitive Guide to Plone(docs.neuroinf.de/PloneBook)の13章の超訳です。UMLの部分は激しく興味がないので略してしまいました。
Archetypesは、Ploneでプロダクトの開発を自動化するためのフレームワークです。コンテンツためのコードがPythonで書かれると、Archetypesはそれ以外のほとんどすべてのこと(ビューを生成したり、フォームを編集したり、など)を処理してくれます。これによってコンテンツタイプを素早く開発したり、最小限のコード量で開発したりすることができるようになります。コードの量が少ないということはバグが潜む可能性が少なくなりますし、Ploneの変化の際にメンテナンスするコードの量も少なくなります。開発サイクルが素早くなり、コストも低くなるわけです。
プロダクトはこういったオブジェクトの記述に基づいているので、そのプロダクトを生成するためのツールを利用することができます。例えば後述する ArchGenXMLはプロダクトをUMLツールで生成することを可能にしてくれます。すなわちUMLモデルの出力をArchGenXMLを通して渡し、 Ploneにプロダクトを素早く登場させることができるわけで、実質なにもコードを書く必要がないということになるわけです。(12章において紹介したような)Pythonでプロダクトを書くことが、少し難しいと感じるならば、この章で紹介することが役に立つでしょう。
しかしこれはArchetypesを使うことがすべてのプロダクトにとって正しいということを意味しているわけではありません。時々 Archetypesがやや冗長に感じることもあります。例えば1つのフィールドを持つコンテンツタイプがあったとして、そこに入るデータに16の異なる順列があるような場合はArchetypesフレームワークを使う意味はほとんどありません。ただしもちろんこれは極端な例で、ほとんどの場合は Archetypesがまさに必要なものであると感じることでしょう。
あるウェブサイトを開発している会社に関して耳にした話ですが、プログラマを連れてクライアントの元へ訪問しクライアントの要望を聞いているときに、プログラマはArchetypesにそれを実装していきました。彼らはミーティングが終わる前に、クライアントに簡単なプロトタイプを提示することができたそうです。
全体的にほとんどのPloneプロダクト開発チームはArchetypesをプロダクト開発の方法として採用していますので、これが概念共有となっており、実際Plone開発の標準となっています。Archetypesの特徴的な機能としては以下のようなものがあります。
- 自動的にビューや編集ページを生成するので、ページテンプレートコードを書く必要がありません
- ユニークなオブジェクトIDを管理します。生成したすべてのオブジェクトはユーザが変更することのできないユニークなIDを持つでしょう。すなわちオブジェクトが移動されない限りは常にオブジェクトを見つけることができるのです。
- オブジェクト間のリファレンスを生成します。それぞれのオブジェクトは他のオブジェクトとたくさんの関係を持つことができます。例えばニュースアイテムにたくさんのリンクオブジェクトを付け加えることができます。
- 標準的なセキュリティをセットアップします。すべてのセキュリティ動作が成されるので、デフォルトのセットアップをしたいときに実は何も変更することはないのです。
- 代替的な保存オプションをもっています。データをZopeの標準データベースではなく、リレーショナルデータベースに保存することができます。
- データ変換が可能です。たとえばMSWordのデータをHTMLに変換できます。
ArchetypesはPloneに特化したものではなく、例えばCMFなどのZopeフレームワークでも利用できますが、現時点ではほとんどPloneによって利用されています。PloneがZope3に移行したときには、ArchetypesとZope3のスキーマは一つにまとまる予定です。すなわちArchetypesを利用することはプロダクトの将来を保証してくれますし、今後のPloneにおいてもうまく動いてくれるでしょう。
この章ではArchetypesを用いて新しいコンテンツタイプを構築していきます。この章は他の章で学習した情報のすべてをまとめあげるもので、いくつかの基本的な概念を通して素早く説明していきます。Archetypesのインストールの仕方を説明した後、基本的なコンテンツタイプを生成するやり方をお見せしましょう。
Archetypesの紹介
ArchetypesはPloneのインストーラやパッケージに同梱されてきますので、すでにArchetypesはインストールされていることと思われます。もしインストールされているかどうかわからない場合には、ZMIのコントロールパネルのProductsを調べることでそれを確認することができます。今回の例ではArchetypes 1.2.5-rc4のPloneでテストを行っています。
Archetypesをインストールするにはhttp://sf.net/projects/archetypesへ行く必要があります。Filesをクリックし、最新のArchetypesをダウンロードします。この章で扱うのはarchetypes-1.2.5-rc4.tgzです。まずは以下のように解凍する必要があります。
$ tar -zxf archetypes-1.2.5-rc4.tgz
$ cd archetypes-1.2.5-rc4
この時点で、何をインストールするか決定する必要があります。インストールする最小限のものはArchetypesディレクトリとgeneratorモジュール、validationモジュールの3つです。これらをインストールするには、これらをインスタンスホームのProductsディレクトリに移動します。私の場合はインスタンスホームが/var/zopeなので、以下のようにコマンドを打ちます。
$ mv Archetypes /var/zope/Products
$ mv generator /var/zope/Products
$ mv validation /var/zope/Products
ArchExampleおよびArchGenXMLは両方ともオプションで、Ploneを動かすのに必須ではありません。しかしこの章では両方を例として用いますのでインストールしておくとよいでしょう
ArchExampleをインストールするにはArchExampleをインスタンスホームのProductsディレクトリへ移動します。
$ cd ..
$ mv ArchExample /var/zope/Products
ArchGenXMLを利用したい場合、これをどこか特定の場所へインストールする必要はありません。忘れないような場所へ置いておけばいいのです。私はふつうこれを他のプロダクトと同じようにインスタンスホームのProductsディレクトリへおいておきます。何も害はありませんし、忘れないようにおいておくだけです。
$ ArchGenXML /var/zope/Products
ArchGenXMLのドキュメントに述べられているように、ArchGenXMLはPyXMLがインストールされていることを必要とします。WindowsやMacのインストーラを用いている場合はこれは既に含まれています。層でない場合にはhttp://pyxml.sf.netへいってパッケージをダウンロードしてください。私の場合には最新のパッケージは0.8.3でしたので、次のようにしました。
$ tar -xvf PyXML-0.8.3.tar.gz
$ cd PyXML-0.8.3
$ python setup.py install
注意:ふつうこのようにインストールすると、root権限を要求されます
これですべてがインストール・セットアップされました。
Archetypesに飛びこむ
Archetypesのすばらしい例が入手可能ですので、何かを構築するよりもArchetypesに付いてくるArchExampleを見ていきましょう。これはArchetypesのパワーを示す例として「article」というコンテンツタイプを追加します。
Article.pyはメインのプロダクトコードを含んでいます。これまでの例とは全く異なるコードが見えるでしょう。これはスキーマを含んでいます。
StringField("blurb",
searchable = 1,
widget = TextAreaWidget(),
),
このコードは「blurb」というコンテンツにアトリビュートがあり、これは文字列で、HTMLのテキストエリアとして表示されることを意味しています。フィールドとウィジェットのすべてのオプションを説明しますが、今はPloneのコンテンツタイプを見てみましょう。下図ではblurbコンテンツタイプを追加しました。
たった4行のコードを書くことでコンテンツタイプにフィールドを追加することができます。この標準的なフォーム要素が表示されたわけです。これを修正するのがどれだけ簡単かを見せるために、フォームのラベルを「Article Blurb」とし、このフィールドを必須にしてみます。これを行うためには以下のように変更します。
StringField("blurb",
required = 1,
searchable = 1,
widget = TextAreaWidget(label="Article Blurb"),
),
ここでは「required = 1」というパラメータを追加しました。これによってこのフィールドは必須となり、パラメータにラベルを付けます。Ploneを再起動し、新しいarticleを追加すると、ユーザインターフェイスはこの新しいスキーマを反映したものに変更されます。このフィールドは「Article Blurb」と呼ばれることになります。
これは単なる修飾的な変更ではありません。この変更は背後にあるスキーマの変更を反映したものであり、これがArchetypesの本当のパワーなのです。これを実際にPythonで書くことと比較すると、大変な重労働から解放されたことがわかるでしょう。すなわち、スキーマを定義すればそれをまとめて即座にArchetypesにまとめることができます。あとはそれを簡単に修正したり、その変更を反映させたりすることができるのです。
これから紹介するいくつかの例を自分で変更したときはPloneを再起動する必要があります。しかしそれだけですべての新しい変更点は読み込まれ、適切に登録されます。
スキーマ、フィールド、ウィジェット
下図はスキーマとフィールドとウィジェットの関係を示しています。
スキーマとベーススキーマ
スキーマを生成するためには、スキーマオブジェクトへタプルとしてフィールドを渡します。例えばarticleスキーマは「group」「blurb」「body」という3つのフィールドを持っています。以下のコードがスキーマのはじめの部分です。
Schema((
StringField('group',
vocabulary=ARTICLE_GROUPS,
widget=SelectionWidget(),
),
# 他のフィールドが続く
)
2つ以上のスキーマを合わせることも可能です。実はこれがArchExampleが実際にやっていることで、コンテンツタイプの中で定義されたスキーマをBaseSchemaと呼ばれるスキーマに追加しているのです。
BaseSchemaは、すべてのPloneのコンテンツタイプが持っている2つの要素である「title」と「ID」を持っています。タイトルはユーザインターフェイスに何かを表示するために必要です。またID、すなわちショートネームは標準的なPloneの慣習に対応しています。これら2つのスキーマはより大きなスキーマを生成するために合わされます。ArchExampleでは既存のBaseSchemaにコンテンツタイプのスキーマを追加することにします。例えば
schema = BaseSchema + Schema((
StringField('group',
vocabulary=ARTICLE_GROUPS,
widget=SelectionWidget(),
),
# 他のフィールドが続く
)
...
アイテムはスキーマのクエリから、スキーマに追加された順番で返されるということに注意しておくとよいでしょう。これはスキーマ中のフィールドを移動させることで、ユーザインターフェイスに表示される順番を再配置できることを意味しています。すなわちBaseSchemaを最初に持ってきているのは、IDおよびtitleフィールドがページの先頭に表示されることを意図したものです。
フィールド
StringFieldフィールドを見てきてわかるように、これはコンテンツタイプの文字列を表しています。Archetypesでは下表に示すようなたくさんのフィールドを利用できます。これからさらに多くのフィールドが追加されるかもしれませんし、必要であれば自分のフィールドを作成することもできます。
Archetypesで利用できるフィールド:
| 名前 | タイプ |
デフォルトウィジェット |
説明 |
|---|---|---|---|
| BooleanField | Boolean values | ComputedWidget | Simple storage of true or false for a field. |
| DateTimeField | Date and time objects | CalendarWidget | For storing dates and times. |
| FileField | Files | FileWidget | Storage for large chunks of data such as plain-text files, Microsoft Word documents, and so on. |
| FixedPointField | Fixed-point numbers | DecimalWidget | For storing numerical data with fixed points. |
| FloatField | Floats | DecimalWidget | For storing numerical data with floating points. |
| ImageField | Image | ImageWidget | Stores an image and allows dynamic resizing of the image. |
| IntegerField | Integer | StringWidget | For storing numerical data as integers. |
| LinesField | Lists | LinesWidget | A list of data such as keywords. |
| PhotoField | Image | PhotoWidget | Same as an image field but has more default image sizes. |
| ReferenceField | Reference | ReferenceWidget | Contains a reference between this object and another. |
| StringField | String | StringWidget | A string field optimized for smaller strings-say, fewer than 100 words. |
| TextField | String | TextWidget | A string field optimized for larger strings say, larger than 100 words. The string can also be transformed into multiple formats. |
from Products.Archetypes.public import BooleanField
すべてのフィールドは同じ方法で初期化します。フィールドを作成し、必須パラメータである「name」を渡します。オプションとして他のキーワードパラメータを渡すこともできます。例えば以下のように。
from Products.Archetypes.public import IntegerField
# 年齢を表す簡単なフィールド
age = IntegerField('age')
それぞれのフィールドは、そのフィールドに割り当てることのできるアトリビュートを持っています。このうち少なくとも2つは既に登場しています(nameとwidgetアトリビュートです)。nameアトリビュートはフィールドに唯一必須のパラメータで、ユニークで、小文字で、スペースやピリオドを含まないものでなければなりません。このnameアトリビュートは内部でのみ用いられるので、名付けルールは重要です。他の値はオプションで、下表ではこれらのアトリビュートを述べます。
フィールドのアトリビュート:
| 名前 | 説明 | 取り得る値 |
|---|---|---|
| accessor | The name of the method to get the value of the field, so you could change how this field is retrieved | Any method name (for example, specialGetMethod) See the 'Overriding Default Methods” section later in this chapter. |
| default | The default value for the field. | Should be appropriate to the field. |
| default_method | A string for obtaining a value for the field; one is created for you by default if you don't define one. | Any string (for example, getSpecialDescription). See the 'Overriding Default Methods” section later in this chapter. |
| edit_accessor | The name of a method to get the raw value of a field. | Any method name (for example, rawGetMethod). See the 'Overriding Default Methods” section later in this chapter. |
| enforceVocabulary | If enabled, you won't accept anything outside the vocabulary. | True or False. |
| index | If you want this field to be placed in its own catalog index, then specify the type of index here as a string. If you append :schema onto the end of the schema, then this will also be added as a metadata column. | The name of any index, such as KeywordIndex or KeywordIndex:schema. |
| name | A unique name for this field. | Any string, lowercase conforming to standard Python variable rules (for example, description, user_name, or coffee_bag_6). |
| mode | The read and write mode of field, as a string; the default is to be read and write. | For read only: r, for write only: w, for read and write: rw. |
| multiValued | If this field can have multiple values, this is useful for things such as drop-down lists. | True or False. |
| mutator | The name of the method to alter the value of the field, so you could change how this field is set. | Any method name (for example, specialSetMethod). See 'Overriding Default Methods” later in this chapter. |
| primary | If True on a field, then this will be the field that responds to File Transfer Protocol (FTP) and WebDAV requests. There can be only field that does this; if multiple are defined, the first one in the schema will be used. You normally do this for the main body attribute. | True or False. |
| required | Specifies that some value for this field required. | True or False. |
| schemata | Place the field into the grouping of other fields called schematas | |
| default | ||
| metadata | ||
| user_information | ||
| searchable | A boolean that specifies if this field will be added to the searchable text and can be used in the searches. | True or False. |
| validators | The validations that will be performed on the field as a tuple of strings; it starts at first and validates against each validation. | Any validator; see the 'Validations of Input” section later. |
| vocabulary | A list of values that a user can choose from, for example, the values to show in a drop-down list. | List of strings (for example, ["Green", "Red", "Blue"]). |
| storage | Where to store the value; the default is Attribute Storage, which stores the field as an attribute of the object. | Any valid storage object such as AttributeStorage or SQLStorage. You can find these in the Archetypes Application Programming Interface (API). For more information, see the 'Storing Your Content in a SQL Database” section later in this database. |
| widget | The widget that will be used to display this field. | Any widget object. |
ウィジェット
ウィジェットはそのオブジェクトが視覚的にどのように表現されるかについての情報を含んでいます。表示されるアトリビュートのビューはそのアトリビュートのタイプに関係していることが多いですが、表示についてのオプションがいくつかあります。ウィジェットはArchetypesのpublicモジュールからインポートすることができます。
from Products.Archetypes.public import BooleanWidget
すべてのウィジェットは同じ方法で初期化します。ウィジェットを作成し、必要とするキーワードパラメータを渡します。
from Products.Archetypes.public import IntegerFiled
from Products.Archetypes.public import IntegerWidget
# 年齢を表す簡単なフィールド
age = IntegerField('age',
widget=IntegerWidget(label="Your age")
)
ウィジェットはそのウィジェットのタイプによってその他のアトリビュートを持つこともできます。ほとんどの場合、これらのアトリビュートはHTMLのアトリビュートに直接対応しています。例えばStringWidgetでは、sizeアトリビュートを設定することができます。これはHTMLのsizeアトリビュートの設定を生成しますので、20文字サイズのinputを設定するためには以下のようなウィジェットにします。
bankAccountNumber = StringField('bank',
widget=StringWidget(
label="Bank account number",
size=20)
)
下表で示すのはArchetypesで利用可能なウィジェットです。
利用可能なウィジェット値:
| 名前 | 説明 | 他の属性 |
|---|---|---|
| BooleanWidget | Shows two checkboxes for the possible values. |
|
| CalendarWidget | Returns a set of input boxes with a link to a helper pop-up box so that a user can select a date. |
|
| ComputedWidget | Returns the computed value as HTML. | |
| DecimalWidget | A simple HTML input box for a string. | size |
| EpozWidget | An HTML Epoz widget that shows the Epoz rich-text editor for the content. | You can provide format, rows, mode, and cols (for columns) |
| FileWidget | Displays an HTML file element for users to upload files. | |
| IdWidget | A simple HTML input box that's used for rendering autogenerated IDs. |
|
| ImageWidget | Shows and allows the editing of images. | You can provide a display_threshold that allows you to set the size of an image; if it's below this size, the image will display in the Web page. |
| IntegerWidget | A simple HTML input box for a string. | size |
| KeywordWidget | This displays a list of keywords from the catalog in a complicated
widget, such as the one in the Properties tab on a normal object. |
|
| LabelWidgets | Used to display labels on forms; no values or form elements. | |
| LinesWidget | Displays a text area that users can enter values. | rows and columns |
| MultiSelectionWidget | A selection widget; by default it's an HTML select widget. | format, which can be one of select or checkbox |
| PasswordWidget | An HTML password element. | |
| RichWidget | Allows the input of a file in multiple formats that are then transformed. See 'Transforming Data' later in this chapter for more information. | You can provide rows, cols *(columns), and *format. |
| ReferenceWidget | Shows an HTML select element of a list of possible references. | |
| SelectionWidget | Shows a selection widget. If it's flex (the default), then if the number of choices is more than four, a select element is used; otherwise a radio button is used. | format, which can be one of flex, select, or radio. |
| StringWidget | A simple HTML input box for a string | size and maxlength. |
| TextAreaWidget | A text area widget that allows the uploading of the content in multiple formats | You can provide allowed_content_types, which is a list of strings; each string represents a meta_type of the type of content uploaded. |
アトリビュートの取りうる値:
| 名前 | 説明 | 取り得る値 |
|---|---|---|
| label | The label that will appear in the user interface with this field. | Any string, for example, Start Date for a field start_date. |
| modes | The modes that this widget will be shown in; by default there are two modes: view and edit. | A list of modes as strings; by default it's ("view", "edit"). |
| populate | If this is enabled, the view and edit fields will be populated. Usually this enabled, but for fields such as a password field, this shouldn't be the case. Usually this is true by default. | True or False |
| postback | If this is enabled, then when an error is raised, the field is repopulated; for fields such as a password field, this shouldn't be the case. Usually this is true by default. | True or False |
| visible | If the attribute should be visible in the user interface. This is a dictionary mapping the view mode to a string, describing the visibility. Choices are visible, hidden (shown in an HTML hidden form value), invisible (not shown at all). | For example, {'view': 'visible', 'edit': 'hidden' } means that the view will show, but the edit page will hide the value. |
フィールドとウィジェットを組み合わせた例
このセクションではふだんよく使われるような便利な組み合わせの例を紹介します。この例では、好きなフルーツのドロップダウンメニューのリストを作成します。これを実現するためには、文字列のリストとしてvocabularyアトリビュートを定義することになります。すなわちフィールドのタイプはStringFieldとなります。そしてウィジェットをSelectionWidgetとして定義することでこれがドロップダウンメニューになります。
StringField('fruit',
vocabulary = ["Apple", "Orange", "Mano", "Banana"],
widget = SelectionWidget(label = "Favourite Fruit")
)
ImageFieldはPloneサイトで画像を生成・管理するのに便利です。ユーザが画像をアップロードできるようなシンプルなフィールドを作るには以下のようにします。
ImageField('portrait',
widget = ImageWidget(label = "My picture"),
)
次の例は非常に複雑なコンテンツタイプです。ほとんどのコンテンツタイプはデータを格納できる一つのメインフィールドを持つでしょう。基本的なdocumentタイプを考えてみると、文章を入力したり編集したりできるbodyフィールドがあるのに気づくでしょう。このbodyフィールドはコンテンツタイプの主体となる文章です。この標準的なフィールドに対していくつかのアトリビュートを追加できます。
まず、このフィールドを検索対象にしたいのではないでしょうか。これにはsearchableアトリビュートを設定します。次にFTPやWebDAVのリクエストに応答させたいと思うかもしれません。これにはprimaryアトリビュートを設定します。また複数のコンテンツタイプをアップロードしたい場合もあります。これにはallowable_content_typesアトリビュートを設定します。そしてもちろん、このフィールドをどのように見せるかということを設定してやります。default_output_typesを設定してやればOKです。ということで以上のような要件を満たすフィールドは以下のようになります。
TextField('body',
searchable = 1,
primary = 1,
default_output_types = 'text/html',
allowable_content_types = ('text/plain',
'text/structured',
'text/restructured',
'text/html',
widget = RichWidget(label = 'Body'),
)
primaryフィールド:整列化、およびFTPとWebDAVに対する反応
Ploneはオブジェクトが多くのアトリビュートを持ち、さらにプレーンファイルとしては表現できない、オブジェクト指向のシステムです。あいにくFTPやWebDAVといった既存のプロトコルのほとんどはプレーンなファイルとしてコンテンツを扱いますので、これらの間で翻訳をしてくれるような何らかの方法が必要です。primaryフィールドはまさにこのことをしてくれます。オブジェクトに対してprimaryフィールドを設定すると、このフィールドはそういったプロトコルでやりとりされるようになります。
もちろんこれは完璧な解決法ではないのですが、FTPやExternal Editorを使うと、オブジェクトのメタデータについてのキーと値を含んだ数値がページのトップへ埋め込まれているのがわかります。これを使うことによってやりとりを行うというのも一つのやり方です。
マーシャリング(整列化)を行うためには、スキーマに「marshall=some_marshaller()」などの記述を加える必要があります。現状では2つのマーシャラがあります。一つはPrimaryFieldMarshallerで、すべてのコンテンツをオブジェクトへ移します。もう一つはRFC822Marshallerで、メールで使われるような、フィールド名と値のペアによってコンテンツを処理します。この章の目的を考えると、コンテンツをExternal Editorで処理するためにPrimaryFieldMarshallerを使うことにしましょう。
入力のバリデーション
コンテンツを編集したときのコンテンツそのものや基本的なエラー(例えば必須項目が無いなど)はうまく処理されますが、もう少し洗練されたエラー処理ができればいいなと思うときがあるでしょう。実はコンテンツが正しく入力されているかどうかをテストするためのバリデーションのセットを使うことができます。例えば、IntegerFieldがあるときには入力されたデータが適切かどうかを確認したいでしょう。
これにはフィールドにvalidatorsパラメータを追加します。例えばIntegerFieldが本当にIntegerになっているかどうかをテストするには次のようにします:
from Products.Archetypes.public import IntegerField
from Products.Archetypes.public import IntegerWidget
# シンプルな年齢のフィールド
age = IntegerField('age',
validators=("isInt"),
widget=IntegerWidget(label="your age")
しかしisIntはどこから来るのでしょう?isIntはバリデーションフレームワークに登録されたバリデータの名前です。数こそ少ないものの、これらのバリデータは非常に便利です(下図に示します)。これらの詳細についてはソースコードを読んだり、正規表現を調べたりすることをおすすめします。これはProductsディレクトリのvalidation/validators/__init__.pyモジュールにあります。
利用可能なバリデータ:
| 名前 | 説明 |
|---|---|
| isDecimal | This validates that the string is a decimal, including positive and negative, exponentials, and so on. |
| isInt | This validates that it's an integer. |
| isPrintable | This validates that this is a letter or a number. |
| isSSN | This validates that it's nine numbers (the length of a U.S. Social Security number). |
| isUSPhoneNumber | This validates that it's ten numbers and is optional. |
| isInternationalPhoneNumber | This validates that it's at least one number and is optional. |
| isZipCode | This validates that it's five or nine numbers. |
| isURL | This validates that the input starts with http://, ftp://, or https://. |
| isEmail | This validates that this conforms to the standard e-mail syntax. |
from validation.validators.validator import RangeValidator
from validation import validation
# RangeValidatorはバリデータの名前、開始値、終了値を取ります
validAge = RangeValidator("validAge", 0, 150)
validation.register(validAge)
そしてフィールドではvalidatorsを以下のように設定します。
validators=("isInt", "validAge")
このようにするとまず最初に整数であることがチェックされ、その後にその整数が年齢の範囲に含まれているかがチェックされます。もしRegexValidatorやRangeValidator以外の全く新しいバリデータを必要としているならば、新規のバリデーションシステムを作成する必要があります。上記の例ではデータが2つの値の間にあることをチェックするバリデーションを作成しましたが、以下ではDateRangeValidatorというバリデータを作って、与えられた日付が指定された日付の間にあるかどうかをbooleanで返してみましょう。これは例えば学校が休暇中であるかどうかなどを確認するときに使えます。
さてそれではまず最初にvalidatorsモジュールの中にDateRangeValidaorというバリデータを定義しましょう。これによって日付の範囲を登録することになります。具体的にはZopeのDateTimeオブジェクトを使うことにします。バリデータはシンプルです。nameを持ち、__call__メソッドに対して反応をするクラスです。以下はvalidatorsモジュールに追加されたDateRangeValidaorです。
from DateTime import DateTime
class DateRangeValidator:
__implements__ = (ivalidator,)
def __init__(self, name):
selft.name = name
def __call__(self, value, *args, **kwargs):
min, max = args[:2]
if not isinstance(value, DateTime):
value = DateTime(value)
return min < value and value < max
Zopeをリスタートすると、この新しいバリデーションを登録することができるようになります。
from validation.validators.validator import DateRangeValidator
from validation import validation
from DateTime import DateTime
christmas = DateRangeValidator("ChristmasHolidays",
DateTime('12/18/2004'),
DateTime('01/09/2005'),
)
validation.register(christmas)
BaseClassのビューとアクションを上書きする
Archetypesは様々な必要性に応じるような標準セットに基づいたビューやアクションをデフォルトで生成します。このアクションはもちろん「表示」、「編集」、「プロパティ」であり、関連づけもまたこのアクションの一つです。あなたはこのオブジェクトに対するビューや編集ページを探そうとはしないでしょう。これらはArchetypesによって自動的に生成されるものです。しかしこれらを上書きすることはできます。
ほとんどの場合はビューメソッドを上書きして自分なりのビューを作りたいのではないでしょうか。デフォルトで提供されるものはかなり基本的なもので、特定のページのために作られたわけではないからです(もちろんPloneのページは平均的なCMSのものよりは良いと思いますが)。しかしページのコンテンツに応じた必要性があるかもしれません。
Archetypesはそれぞれのコンテンツタイプのクラスを生成します。Archetypesでは、12章で述べたようなソースコードタイプのクラスを作成するのとほとんど同じように、自分のコンテンツタイプのための基本的なクラスを生成することが可能です。この基本クラスはBaseContentと言って、Archetypesのpublicモジュールからインポートすることができます。このBaseContentクラスはArchetypesが知る必要のあることをすべて定義しています。このクラスを作成することで自分がしたいことのほとんどすべてを上書きすることができるでしょう。
今示してきたように、これには2つのパーツがあります。まずはfactoryタイプの情報によって利用されるアクションを作成することです。Archetypesではクラスにactionアトリビュートを付与することでこれを行うことができます。例えば:
from Products.Archetypes.public import BaseContent
class Article(BaseContent):
# 他のコード
actions = ({'id': 'view',
'name': 'View',
'action': 'string:&{object_url}/article_view',
'permissions': (CMFCorePermissions.View,)
},)
次に実際のビューのための(上記コード内で指定した)article_viewという名前でページテンプレートを生成する必要があります。この文字列はこのプロダクトにおいてコンテンツタイプが存在するページテンプレートを定義します。今回の例の場合はArchExampleの「skins/archexample」ディレクトリにこのページテンプレートのコピーを確認することができます。
変更を確認するためにはPloneをリスタートする必要があります。またportal_typesツールにインストールされたアクションを変更しますので、このアクションを変更した場合はプロダクトを再インストールする必要があるでしょう。これはPloneのサイト設定から行うことができます。
またfactoryタイプの情報に含まれるすべての要素はアトリビュートを生成することで上書きできます。例えばcontent_iconの設定を上書きするためにはcontent_iconというアトリビュートを作成すればよいわけです。
class SomeProduct(BaseContent):
"""Some product"""
content_icon = "some_icon.gif"
デフォルトメソッドを上書きする
デフォルトメソッドのいくつかを上書きするオプションについては前述しましたが、ここではフィールドの編集のされ方を操作する、より高度なオプションについて述べます。
アトリビュートやフィールドにはアクセサやミューテータを用いてアクセスすることができます。いくつかのデフォルトメソッドは利用可能な状態になっています。例えばblurbというフィールドがあれば、getBlurbメソッドやsetBlurbメソッドを用いることでアクセスすることができます。
しかし、実際にはもう少し別の何かをしたいという場合もあるでしょう。例えば会社の名前のスペルをチェックするためにフィールドの値をフィルタしたい、とか、あるフィールドが変更されたときに他のフィールドの値も同時に変更したいなどということがあるかもしれません。こういったことはデフォルトメソッドを上書きすることで実現することができます。以下の例ではgetSpecialBlurbメソッドを作成します。これは入力されたblurbを操作した後にクライアントに返すようにするメソッドです。具体的には「Perl」というテキストを「Python」にします。
class Article(BaseContent):
def getSpecialBlurb(self):
"""The view for an article"""
blurb = self.getField('blurb').get(self)
blurb = blurb.replace('Perl', 'Python')
return blurb
実際には、フィールドがこのメソッドを使うように変更する必要もあります:
StringField('blurb',
searchable=1,
widget=TextAreaWidget(),
accessor="getSpecialBlurb",
)
この例では「表示」や「編集」ページにおいてblurbフィールドがアクセスされるときは常にgetSpecialBlurbの値が返されるようになります。Archetypesにはaccessorパラメータによってこのメソッド名が渡されているので、Archetypesはこのメソッドにアクセスすべきであることを知っています。ただしここにはちょっとトリッキーな方法も含まれています。アトリビュートの生の値にアクセスするためにはこのフィールドを得る必要があるので、getメソッドを使っています(「blurb = self.getField('blurb').get(self)」の部分)。フィールドをgetしてメソッドを呼ぶのはArchetypesでは非常によくあるやり方です。
コンテンツタイプをまとめあげる
ここまででコンテンツタイプの主要な要素についてまとめてきました。以下はすべてのコードを合わせたものです。Archetypesがいろいろなことをやってくれるので、Pythonで書いたプロダクトよりもよりコンパクトになっています。
from Products.ArchExample.config import ARTICLE_GROUPS
from Products.Archetypes.public import BaseSchema, Schema
from Products.Archetypes.public import StringField, TextField
from Products.Archetypes.public import SelectionWidget, TextAreaWidget
from Products.Archetypes.public import RichWidget
from Products.Archetypes.public import BaseContent, registerType
from Products.Archetypes.Marshall import PrimaryFieldMarshaller
from Products.CMFCore import CMFCorePermissions
from config import PROJECTNAME
schema = BaseSchema + Schema((
StringField('group',
vocabulary=ARTICLE_GROUPS,
widget=SelectionWidget(),
),
StringField('blurb',
searchable=1,
widget=TextAreaWidget(),
),
TextField('body',
searchable=1,
required=1,
primary=1,
default_output_type='text/html',
allowable_content_types=('text/plain',
'text/structured',
'text/restructured',
'text/html',
'application/msword'),
widget=RichWidget(label='Body'),
),
),
marshall=PrimaryFieldMarshaller(),
)
class Article(BaseContent):
"""This is a sample article; it has an overridden view for show,
but this is purely optional
"""
schema = schema
actions = ({
'id': 'view',
'name': 'View',
'action': 'string:${object_url}/article_view',
'permissions': (CMFCorePermissions.View,)
},)
registerType(Article, PROJECTNAME)
冒頭部にあるimport文を除けば、このコードのほとんどの部分はすでに説明しました。が、最後にあるregisterTypeについてはまだ言及していません。この関数はオブジェクトをプロダクトに登録するものです。プロダクトは複数のコンテンツタイプを持つことができるため、オブジェクトとプロジェクト名を引数に取ります。上記のコードの場合は、プロジェクト名には設定ファイルからimportされたものが使われます(冒頭部で「from config import PROJECTNAME」としている)。設定ファイルであるconfig.pyは下記のようにプロダクトの設定のための変数を含んでいます。
from Products.CMFCore.CMFCorePermissions import AddPortalContent
from Products.Archetypes.public import DisplayList
ADD_CONTENT_PERMISSION = AddPortalContent
PROJECTNAME = "ArchExample"
SKINS_DIR = 'skins'
GLOBALS = globals()
ARTICLE_GROUPS = DisplayList((
('headline', 'Headline'),
('bulletin', 'Special Bulletin'),
('column', 'Weekly Column'),
('editorial', 'Editorial'),
('release', 'Press Release'),
))
ARTICLE_GROUPS変数は、groupウィジェットで使う文字列のタプルのタプルで、単に文字列のタプルを使っても良いのですが、ここではDisplayListクラスを利用しています。これを使うことでユーザに他の候補も見せることができます。上記の場合、ArticleGroupsに対するHTMLは下記のようにselect要素で表示されます。
<option value="headline">Headline</option>
<option value="bulletin">Special Bulletin</option>
...
またここではglobals関数も利用しています。これはPythonの組み込み関数で、すべてのグローバルなシンボルを含んでいます。これはスキンのファイルシステムディレクトリのビューを作ることができるように、ファイルシステム上のskinsディレクトリへのパスを計算してくれます。
プロダクトの初期化巻数である__init__.pyもシンプルです。プロセスとリストタイプ関数は以下のようになります。
from Products.Archetypes.public import process_types, listTypes
content_types, constructors, ftis = process_types(
listTypes(PROJECTNAME),
PROJECTNAME)
listTypes関数はArchetypesのユーティリティで、登録したすべてのタイプを返します。これがprocess_types関数に渡され、これによってすべてのコンテンツタイプとコンストラクタ、およびファクトリタイプ情報(FTI)などのオブジェクトが返されます。これらはすべて(Archetypesではない)Pythonプロダクトにおいて登録したアイテムと同じものです。すなわちこのユーティリティ関数を利用することで登録に関する部分がより容易になるのです。
最後にExtensionsディレクトリにあるinstall.pyを見てみましょう。このスクリプトにはちょっとうろたえるかもしれません。なぜならinstallTypesとinstall_subskinという2つのユーティリティ関数で処理されているからです。
from Products.Archetypes.public import listTypes
from Products.Archetypes.Extensions.utils import installTypes,
install_subskin
from Products.ArchExample.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()
ArchExampleのすべてはhttp://plone-book.agmweb.caから入手することができます。もちろんこれを実際にプロダクトとして動かすこともできます。ここまでで見てきたように、このコンテンツタイプは多くのコードを書くことなく素早く簡単に開発でき、また変更することも簡単です。
Archetypesベースの開発
このセクションではArchetypesの高度な機能をいくつか紹介します。これらの機能はコンテンツタイプを開発するのに便利ないくつかのツールを提供してくれるでしょう。たとえば関連づけを行ったり、新しくウィジェットを作成したり、コンテンツを変換したりといったことです。
まずは変更を加えたときにそれがどのようにしてPloneへ伝わるのかということを理解することが大切です。これまで見てきたようにプロダクトをセットアップするには様々な段階があります。プロダクトを変更したときにどの段階から始めればよいのかを理解しましょう。
スキンに何か変更を加えたときには、ただデバッグモードで動かせば変更が反映していることを確認できます。アクションやアイコンなど、portal_actionsへの変更を行った場合にはPloneをリスタートし、プロダクトを再インストールする必要があります。
スキーマを変更した場合にはPloneをリスタートするだけです。そうすれば新しいインスタンスはアップデートされるでしょう。では古いインスタンスはどうなるのでしょう?幸運にもArchetypesはそういった古いインスタンスをダイナミックに新しいスキーマへアップデートしてくれるツールを提供してくれます。ZMIでarchetype_toolをクリックしましょう。
ユニークIDを使う
ユニークIDのコンセプトはシンプルですが、Zopeには欠けています。もともとZopeの開発者はオブジェクトへのパスを利用しようとしていましたが、残念ながらこれは適当ではありませんでした。例えば誰かがあるドキュメントの位置を変更したとしましょう。このときそのドキュメントへのユニークなリファレンスは失われてしまいます。ユニークIDは本当に便利なツールです。ArchetypesはすべてのオブジェクトにユニークIDを生成し、そのオブジェクトへのリファレンスを保存します。これはuid_ctalogという別のカタログへ保存されます。
uid_catalogはZMIから見ることができます。これはAPIとインデクスを欠いているという点を除いてportal_catalogとほとんど同じです。このカタログによってオブジェクトを得ることは非常に簡単です。オブジェクトをカタログから探すだけです。例えば以下のScript (Python)オブジェクトを使えば、既知のUIDによって登録されたオブジェクトを探すことができます。
##paramaters=objectId
results = context.uid_catalog(UID=objectId)
return results[0].getObject()
しかし本当に便利なのは既存のオブジェクト間のリファレンスを考えるときです。例えば記事に関係する1つ以上の別々の場所に存在する画像を考えてみましょう。これらの画像は他のユーザによって別の時間にアップロードされている可能性があります。もしかしたら画像データベースの一部かもしれません。これらの画像がArchetypesオブジェクトであれば、articleスキーマに以下のようにフィールドを追加します。
ReferenceField("images",
allowed_types=("Archetype Images",),
multivalued=1,
),
これでユーザはすべてのArchetype Imageオブジェクトのドラッグドロップメニューを使って、そこから画像を選ぶことができるようになります。
このスキーマの裏では、カタログを通したUIDのリファレンスが生成されています。リファレンスに関するすばらしい例がACMEというプロダクトに見ることができます(http://sf.net/projects/archetypes)。
ウィジェットを変える
何故このウィジェットはこういうふうに動作するのか、とか、何故このウィジェットはこのように見えるのか、とかいったFAQを耳にします。これに対する答えは、そのように書かれているからだ、となります。ウィジェットはクライアントが目にするものなので、これに対する要求はしばしばクライアントによるものです。
すべてのウィジェットはファイルシステム上にあるページテンプレートマクロによって表現されます。すなわちウィジェットを変更したり自分で書いたりするのは非常に簡単です。
viewマクロは「表示」ページで表示され、リードオンリーで、ユーザフレンドリーなアイテムビューです。editマクロは「編集」ページで表示され、ユーザがデータを編集するためのページです。searchマクロは特定のコードに対応するページを集めるときに呼ばれます。これはeditマクロのように見える場合もあります。stringフィールドはstringフィールドとして編集され、文字列として表示されるかもしれませんが、ドロップダウンメニューを使って検索されます。
例としてメールアドレスを含むstringフィールドがあり、これをクリッカブルなEメールリンクとして表示する場合を考えてみましょう。まずはマクロを書く必要があります。今回の場合はeditマクロやsearchマクロを別個に書く必要はありません(それらも結局ただのstringですから)。さぁそれではemail_widget.ptというページテンプレートを以下のように作成して、プロダクトのskinディレクトリへ置いておきましょう。
<html xmlns:tal="http://xml.zope.org/namaspaces/tal"
xmlns:metal="http://xml.zope.org/namaspaces/metal"
i18n:domain="plone">
<body>
<div metal:define-macro="edit">
<div metal:use-macro="here/widgets/string/macros/edit" />
</div>
<div metal:define-macro="search">
<div metal:use-macro="here/widgets/string/macros/search" />
</div>
ビューでは文字列をmailtoリンクとしてみせる必要がありますので、以下のように調整しておきます。
<div class="field" metal:define-macro="view">
<a href="#" tal:attributes="href string:mailto:${accessor}"
tal:content="accessor">email</a>
</div>
</body>
</html>
これでページテンプレートを定義できました。あとはただマクロのテンプレート名をウィジェットに組み込むだけです。以下のコードではe-mailフィールドを通常のStringWidgetとして定義し、ここで利用するマクロをemail_templateマクロに変更しています。
StringField('email',
validators = ('isEmail',),
widget = StringWidget(
label='Email',
macro='email_template'
)
)
この時点では既存のウィジェットのマクロを変更しただけです。完全に新しいウィジェットを作成するには新しいウィジェットを定義し、これを登録します。すべてのウィジェットは同じベースクラスを共有しています。以下が新しいモジュールである(ArcheExampleディレクトリの中にある)EmailWidget.pyとなります。
これによって新しいウィジェットが生成され、レジストリに登録されます。ウィジェットのプロパティがテンプレートの値にセットされていることに注意してください。
from Products.Archetypes.Widget import TypesWidget
from Products.Archetypes.Registry import registerWidget
class EmailWidget(TypesWidget):
_properties = TypesWidget._properties.copy()
_properties.update({
'macro' : "email_template",
'size' : '30',
'maxlength' : '255',
})
registerWidget(EmailWidget,
title='String',
description='Renders a clickable email field',
used_for=('Products.Archetypes.Field.StringField',)
)
これをarticleに含めるためにはEmailWidgetを直接importしますが、マクロを明示的に定義する必要はありません。
from EmailWidget import EmailWidget
StringField('email',
validators = ('isEmail',),
widget = EmailWidget(
label='Email',
)
)
フォルダ風オブジェクトの開発
あなたは既にPloneでフォルダ風オブジェクトを使っていることでしょうが、もしかしたらまだこれを理解していないかもしれません。フォルダ風オブジェクトはフォルダやディレクトリの性質を持っており、これはすなわち個別のオブジェクトをこれに含むことができると言うことです。フォルダ風オブジェクトには特別なことは何もありません。ただプロパティを持ったり新規のコンテンツを加えたりできるようにするために特定のベースクラスを継承しているだけです。
フォルダ風オブジェクトはいくつかの理由でとても便利なものです。例えば個々のオブジェクトを一カ所に集めておくことができますし、標準的なユーザインターフェイスでコンテンツを管理したりすることもできます。一般的に、フォルダはきわめてシンプルにしておき、ロジックはオブジェクトやワークフローに組み込むべきです。しかしながら何事にも例外はつきもので、collectorとしてはCMFCollectorやPloneCollectorが例としてあげられます。これらはとても複雑なコレクタオブジェクトです。
フォルダ風オブジェクトを作成する簡単な方法は、Archetypesを使うことです。フォルダ風でないオブジェクトを生成するのに必要な仕事をすべて処理してくれるBaseContentタイプがあることはすでに見てきました。さて、実はフォルダ風オブジェクトを作成するためのものをすべて含んだBaseFolderコンテンツタイプというものも存在します。フォルダのための特別なスキーマがあるのがわかるでしょう。フォルダ風オブジェクトは通常descriptionも持っています。フォルダ風のコンテンツタイプを作成するためにはベースクラスとスキーマを変更するだけです。例えばきわめてシンプルなフォルダは以下のようになります:
from Products.Archetypes.public import BaseFolder, BaseFolderSchema
schema = BaseFolderSchema
class SimpleFolder(BaseFolder):
"""A simple folderish archetype"""
schema = schema
registerType(SimpleFolder)
フォルダの中にあまりにもたくさんのコンテンツを保存しようとすると、効率が悪くなる傾向にあります(だいたい100オブジェクト)。通常のフォルダ風オブジェクトは少数のオブジェクトを素早く扱うようにデザインされているからです。もし大量のオブジェクトを扱いたいのなら、バイナリツリーフォルダを使うことができます。これはオブジェクトを、Pythonのディクショナリよりもより効率的なバイナリツリーとして保存します。これを使うためにはBaseBTreeFolderおよびBaseBTreeFolderSchemaをインポートするだけです。プロダクトの開発に限って言えばこれらは同じように動作しますが、そこに大量のデータを置かない限り特に違いは感じられないでしょう。私は100000以上のオブジェクトをBTreeFolderに保存していますが、いつもうまい具合に反応してくれています。
Microsoft Officeのコンテンツを処理する
WordやExcelなどのMicrosoft Officeドキュメントを処理することは、すべてのCMSがいつか直面する難問です。しかしこれはMicrosoft Officeのみならず、OpeOfficeやpdf、画像などすべてのタイプのコンテンツにもあてはまることでしょう。これらのコンテンツをウェブサイトで扱うことはいくつかの問題を引き起こすことがよくあることが、当然のようによく知られています。これらを編集するのはとても難儀をします。なぜならドキュメントをクリックするとダウンロードしたり、もっと悪い場合にはブラウザの中でそれを開いてしまうからです。編集が終わったら今度はいろんなところを何回もクリックしてそれをウェブサイトにアップロードしなければなりません。また、コンテンツはオンラインでありながら検索対象には含まれません。なぜならそれらはプレーンテキストではない、カタログが理解不能なフォーマットだからです。さらにそれらはウェブフレンドリーなフォーマットではないため、コンテンツをオンラインで見ることもできません。
こういったコンテンツを編集する問題について解決策が2つあります。ユーザのほとんどがWindowsを使っているならばWebDAVを使うことが一つの策ですが、Microsoftのウェブフォルダの実装にはやや問題があるのでこの策を採る場合には注意が必要です。もう一つの策として、ExternalEditorはWebDAVの仕組みをうまく機能させています。PloneがExternalEditorに「これはWordファイルだ」と教えてやればWordファイルとして開くことでしょう。
Archetypesはコンテンツタイプの変換を処理するPortal Transformsという組み込みの変換パッケージを持っています。例えばあるフォーマットのファイルをHTMLへ変換し、これをカタログ化し、ユーザインターフェイスでHTMLを表示する、といったことをすることができるのです。これはデータを変換し、その結果を読み込むための外部処理を利用することによって行っています。例えばWindowsを使っているなら、Portal TransformsはアップロードされたWordドキュメントをCOMオブジェクトをスタートします。
このようなことが舞台裏ですべて行われています。気にかけるべきことは変換のための要件が満たされ利用可能になっているかという、ただ一つのことだけです。たくさんの異なる変換形式を扱うこともできますし、データを変換するやり方は一つではありません。
OpenOffice.orgはMicrosoftのコンテンツの華麗な変換を提供してくれているので、Microsoftでないプラットフォームでもこれを変換することができます。wvWareを使ってもうまく変換することができます。これらの選択肢はもちろん利用可能ですが、別に特別な設定が必要なわけではありません。これらを一度セットアップしてしまえばMicrosoftサーバを利用していたときよりも質の良い変換を得ることができます。
まずは何を変換したいのかということを考えると良いでしょう。そしてPortal Transformsのソースコードを眺めてみてください。そこに自分のやりたい変換があればそれを使えばよいのです。
コンテンツタイプを設定する
ここではMicrosoft Wordドキュメントを処理するための簡単なコンテンツタイプを作成してみましょう。これはおそらく一番処理しなくてはいけないようなコンテンツでしょう。サンプルを示すためにWindows上での変換を行っていますが、おそらく他のシステム、Linuxでもたぶん動くことでしょう。今回のケースではPloneインスタンスをWindowsにインストールことです。これはWin32APIモジュールをインストールし、動かしています。サーバにMicrosoft Officeがインストールしてあり、これを処理するコンテンツタイプを作成する必要があるという状況です。実質的にはスキーマの中の一つのフィールドがこれを処理しますので、スキーマは以下のようになります:
schema = BaseSchema + Schema((
TextField('body',
searchable=1,
required=1,
primary=1,
default_output_type='text/html',
allowable_content_types=('application/msword',),
widget=RichWidget(label='Body'),
),
),
marshall=PrimaryFieldMarshaller(),
)
これまでの例と異なるのはWordドキュメントに対するMIMEタイプ(application/msword)を追加した部分です。コンテンツタイプが変換されるためにはMIMEタイプをallowable_content_typesに追加します。例えばWordとPDFドキュメントを処理したいのであれば以下のようにします:
allowable_content_types=('application/msword','application/pdf'),
このコンテンツタイプは今の時点ではわかりやすさのために非常にシンプルにしていますが、さらにプロパティを追加することもできます。もっといろんなことをしたいと思えば、例えばWordドキュメントからメタ情報を抜き出してこれをPloneの中にメタデータとして保持しておくこともできます。
Windowsで変換をセットアップする
変換はportal_transformsツールの内部で自動的にセットアップされます。しかしツールの中にはさほど多くのものがあるわけではありません。利用可能な変換のリストを提供しているだけにすぎません。変換は入力としてのMIMEタイプ(例えばapplication/msword)とそれが変換され出力される出力(例えばtext/html)のリストを持っています。それぞれの変換はファイルシステム上のモジュールです(例えばPortalTransforms/transforms/word_to_html)。
しかし変換を作動させるためにはそのCOMのPythonバインディングを生成するためのツールを実行する必要があります。これを行うには、スタートメニュー→Plone→Pythonwinを選び、COM Makepy Utilityを選択します。このユーティリティは利用可能なCOMインターフェイスをリストアップしてくれます。OfficeがインストールしてあればWord Library(8.1)を見つけることができるでしょう。このオプションを選びOKボタンをクリックします。これでPythonwinは適切な情報を生成してくれます。これを行うと「Generating to...」というメッセージが返ってくるでしょう。
ここまでくればもうPythonwinを閉じてもかまいませんので、Ploneをリスタートしましょう。
コンテンツタイプをテストする
コンテンツタイプをテストするためにはPloneをリスタートし、このプロダクトが正常に登録され、アプリケーションの追加と削除のメニューからインストールすることができることを確認する必要があります。Wordドキュメントを追加するには、Ploneのインターフェイスへ行き、ドロップダウンメニューの中からWordドキュメントを選びましょう。これは小さなWordロゴが見えるはずですのですぐわかるでしょう。
次にファイルシステム上のファイルを選択し、これをアップロードするために保存ボタンをクリックします。これでファイルはアップロードされ、表示ページへ移るでしょう。このときドキュメントをアップロードしさらに変換するので、少し時間がかかるかもしれません。これでこのドキュメントをHTMLで見ることができたはずです。
これはアップロードされたわけですから編集することもできます。これにはExternal Editorを利用するのがよいでしょう。External Editorのクライアントアプリケーションをすでにインストールしてあるならば表示ページの右上にあるペンシルアイコンをクリックすればこれをWordで開くことができます。あとはWordドキュメントを編集するだけです。このファイルを保存するたびごとにPloneに読み込まれ、変換されます。
UMLダイアグラムでコンテンツタイプを記述する
(略)
コンテンツをSQLデータベースへ保存する
この本のほとんどの部分ではコンテンツをZopeオブジェクトデータベースへ保存してきました。またコンテンツをファイルシステム上に保存するやり方も紹介しましたが、よくある質問というのは、どのようにリレーショナルデータベースへ保存するのか、というものです。以下のような場合にはコンテンツをリレーショナルデータベースへ保存するのは非常に有効です。
- 構造が固定しており、スキーマがあまり変化しない
- リレーショナルデータベースにアクセスできる他のアプリケーションを利用している
- すでにリレーショナルデータベースによって実現されている要件がある
- 頻繁にアップデートされる大量のデータがある
一昔前の伝統的なCGI環境では、コンテンツをリレーショナルデータベースへ保存するためにSQLを書いていたでしょう。もし本当にSQLを書きたいならばZSQLメソッドを利用して書くことができます。これは多くの場合に非常に便利で、実際Zope Bookやその他の書籍でも詳細に説明されていますが、これはコンテンツを直接保存することはできません。ZSQLメソッドはSQL文を保存し、それに基づいてリレーショナルデータベースへクエリを送ります。シンプルなクエリを送る場合には非常に便利ですが、Pythonクラスやコンテンツタイプは保存しません。これはPloneの考え方には沿いませんが、Zope Bookにはすばらしい記事があります(http://zope.org/Documentation/Books/ZopeBook/2_6Edition/RelationalDatabases.stx)。
コンテンツタイプをリレーショナルデータベースへ保存する
コンテンツタイプを保存するためには、リレーショナルデータベースをセットアップし、Zopeからコネクションを張ります。ここでは普段利用しているPostgresを使った場合を考えてみます。Postgresはあらゆるレベルのユーザにとって利益をもたらしてくれます。ZopeをPostgresに接続するためにはpsycopgやZPsycopgを利用できますが、psycopgを使うのがよいでしょう。http://initd.org/software/psycopgにはDebianパッケージやWindowsパッケージを見つけることができます。
データベースアダプタをインストールし、Zopeからリレーショナルデータベースへコネクションを張りましょう。この点についてはhttp://zope.org/Documentation/Books/ZopeBook/2_6Edition/RelationalDatabases.stxを参照してください。簡単に説明すると、ZMIへいって、ドロップダウンメニューからデータベースコネクションを追加し、リレーショナルデータベースに接続するための設定を行います。特定のユーザでリレーショナルデータベースへ接続する場合は、そのユーザがデータベーステーブルをcreate、insert、update、deleteできる権限を持っているかどうかを確認しておいてください。create、delete権限はデバッグ時にスキーマやオブジェクトを追加したり削除したりするのに必要です。一度開発に区切りが付いてプロダクションフェーズへ入ったならばこれは必要ありません。
次にZMIにてarchetypes_toolをクリックし、Connectionsを選択します。ここでコンテンツタイプとデータベースアダプタのタイプをマッピングします。一番簡単なのはデフォルトをデータベース接続にマッピングすることです。お望みなら異なるデータベースへ特定のタイプをマッピングすることもできます。さぁここでこの新しい保存場所を利用するようにスキーマを変更しましょう。SQLStorageから適切なストレージクラスをインポートするのを忘れないでくださいね(この場合はPostgreSQLStorageをインポートする)。
リレーショナルデータベースへ保存したフィールドのstorageパラメータを設定しましょう。様々なフィールドをリレーショナルデータベースへ保存できることに気づきましたか?すべてのフィールドをリレーショナルデータベースへ保存することも可能ですし、いくつかのフィールドだけをリレーショナルデータベースへ保存することもできますが、少なくともIDを持っているオブジェクトはPloneのデータベースへ保存するのがよいでしょう。下記の例ではinteger(age)、string(email)というPostgresに保存しています。
from Products.Archetypes.public import BaseSchema, Schema
from Products.Archetypes.public import BaseContent, registerType
from Products.Archetypes.public import IntegerField, StringField
from Products.Archetypes.public import IntegerWidget, StringWidget
from Products.Archetypes.SQLStorage import PostgreSQLStorage
from config import PROJECTNAME
schema = BaseSchema + Schema((
IntegerField('age',
validators=("isInt",),
storage = PostgreSQLStorage(),
widget=IntegerWidget(label="Your age"),
),
StringField('email',
validators = ("isEmail",),
index = "TextIndex",
storage = PostgresSQLStorage(),
widget = StringWidget(label='Email',)
),
))
class PersonSQL(BaseContent):
"""Our person object"""
schema = schema
registerType(PersonSQL, PROJECTNAME)
最後にPloneをリスタートし、コンテンツタイプをPloneへ登録しましょう。これでこのコンテンツタイプをテストする準備ができました。PersonSQLオブジェクトを生成すると、いつも通りにコンテンツタイプは処理されるでしょう。もちろん実際のテストはデータベースを見てみればわかります。
データベースの中にpersonsqlというテーブルができていることがわかるでしょう。またそのテーブルの中にはuid、parentuid、age、emailといったカラムができています。psqlを使うと以下のようになります。
db=# \d
List of relations
Schema | Name | Type | Owner
--------+--------------------------+----------+-------
public | personsql | table | www-data
...
db=# \d personsql
Table "public.personsql"
Column | Type | Modifiers
-----------+------+-----------
uid | text | not null
parentuid | text |
age | int |
email | text |
Indexes: personsql_pkey primary key btree (uid)
ageカラムはint型でできていますし、emailカラムはテキスト型でできています。これらはSQLStorageの中で生成されたマッピングですが、好きなように適切なものへと変更することができます。uidカラムはPlone内でのオブジェクトのユニークIDです。またparentuidは親オブジェクトのuidです。これらはすべてArchetypesに対するユニークIDです。例えば:
db=# SELECT * FROM personsql;
uid | parentuid | age | email
---------------------------+-----------+-----+-------------------
PersonSQL.2003-07-23.4935 | | 30 | andy@clearwind.ca
データがリレーショナルデータベースへ保存されたことがわかります。SQLを書く必要が全くないにもかかわらず、リレーショナルデータベースの利点を使うことができます!Joel BurtonがSQLStorageに関するすばらしい記事を書いており(http://plone.sourceforge.net/archetypes/sqlstorage-howto.html)、彼の好意から、この章のいくつかの部分はJoelのドキュメントに基づいています。

