Создание новых типов контента с помощью Archetypes
ПРИМЕЧАНИЕ: Это вид всех страниц руководства удобный для печати. Постраничное представление доступно здесь, если Вам так удобнее.
1. Введение
1.1. Что такое Архетипы?
Преимущества использования Архетипов в сравнении с контент-типами CMF:
-
автоматическая генерация форм и видов
-
библиотека типов полей, виджетов форм и валидаторов полей
-
возможность определения собственных полей, виджетов и валидаторов
-
автоматические преобразования для «rich content»
-
встроенная система ссылок, которая дает возможность связывания двух объектов с помощью отношения.
Начиная с Plone 2.1 Архетипы де-факто являются основным способом создания новых типов контента и используются в большинстве дополнительных продуктов.
1.2. Схема Архетипа
Архетипы предоставляют прозрачную среду для хранения атрибутов объектов контента. Эта среда состоит из Полей хранимых в контейнерах называемых Схема. Поля — это специализированные классы Python, которые позволяют хранить и осуществлять доступ к данным ассоциированным с объектом.
Поля предоставляют новую функциональность: есть специализированные типы полей для строк, списков строк, целых, чисел с плавающей точкой и т. д., что позволяет манипулировать полями по-разному, в зависимости от хранимого типа данных.
Определения
Давайте определим часто используемые термины:
Поле (Field)— экземпляр класс Field определенного в Схеме
Схема (Schema)— конейнер, который используется для хранения полей
Схемата (Schemata) — именованная группа полей. Одна схема может содержать несколько схемат.
AT — аббревиатура для Архетипов
Поля, Классы и Объекты
Поля Архетипов — это объекты Python, содержащиеся в схеме. Поле определяется один раз для класса содержимого. Единственный экземпляр поля используется для каждого экземпляра класса. Следовательно, отношение между экземплярами Поля и классами контента можно определить следующим образом: «экземпляр поля принадлежит только одному классу». Однако, класс может содержать много разных экземпляров полей. Более того, каждый экземпляр AT класса использует единый набор полей. AT объекты сами по себе не содержат уникальных полей.
При старте Zope в процессе инициализации продукта Архетипы считывают схемы зарегистрированных классов и автоматически генерирует методы для чтения (accessor) и изменения (mutator) каждого из объявленых полей.
Шаблоны схемы
Архетипы включают в себя три шаблона схем:
- BaseSchema определяет обычный тип контента
- BaseFolderSchema определяет тип контента ведущий себя как папка (объект может содержать другие объекты)
- BaseBTreeFolderSchema используется для папок, которые должны манипулировать сотнями или тысячами объектов (может даже и миллионами)
Все три типа схем включают в себя id и title, а также стандартный набор полей метаданных Dublin Core.
Изменение полей существующей схемы
Изменение существующего поля схемы возможно с помощью следующего синтаксиса:
schema['<field_name>'].attribute = value
Например, чтобы изменить название виджета поля description (уже доступного в BaseSchema), вы можете написать (в своем собственном определении схемы на основе BaseSchema):
schema['description'].widget.label = u'Summary'
Поля схемы упорядочены и обычно первые поля показываются первыми в формах добавления и редактирования контента. Если нужно изменить порядок полей в схеме, используйте метод moveField:
- Поместить
поле до другого поля:
schema.moveField('<field_to_move>', before='<field_to_place_it_before>') - Поместить поле после
другого поля:
schema.moveField('<field_to_move>', after='<field_to_place_it_after>') - Поместить поле наверх
схемы:
schema.moveField('<field_to_move>', pos='top') - Поместить поле вниз
схемы:
schema.moveField('<field_to_move>', pos='bottom') - Переместить поле в
указанную позицию:
schema.moveField('<field_to_move>', pos=0)
1.3. Что такое ATContentTypes?
ATContentTypes — это продукт ядра Plone, который поставляет базовые контент-типы (включен в ядро в Plone 2.1 и выше).
Одно из основных изменений в Plone 2.1 было то, что базовые контент-типы (Page, Image и т.д.) были основаны на Архетипах вместо типов CMF. Новые базовые типы были определены в продукте ATContentTypes.
ATContentTypes предоставляет некоторое количество базовых классов и инструментов, которые обеспечивают общее поведение Plone. В частности, он включает в себя поддержку меню «display» и меню «more» и ограничения для меню «add item».
Вы можете использовать базовые классы ATContentTypes и инструменты в своих собственных продуктах. Руководство по RichDocument охватывает базовые техники и возможно его изучением стоит продолжить обучение после прочтения данного руководства.
2. Пример AT продукта
2.1. Вступление
В этой части руководства мы рассмотрим пример AT продукта, чтобы объяснить CMF/Archetypes на практике. Мы создадим продукт example.archetype, содержащий реализацию контент-типа InstantMessage — добавление сообщений пользователями с определенными правами для чтения другими пользователями. Однако, как вы можете догадаться, это будет скорее учебный пример, чем рабочий продукт для реального веб приложения.
Что такое продукт?
Продукт — Zope продукт, если быть точным — это сторонний аддон, который добавляет новую функиональность. Это пакет с кодом, написанным на Python с соблюдением определенных договоренностей и правил.
Для понимания этого раздела вам понадобятся базовые знания работы с файловой системой и протоколов программирования общих для Python и Zope.
example.archetype будет иллюстрировать следующие возможности CMF и Archetypes:
- базовые поля и виджеты
- определение и использования словаря для поля с виджетом выбора (selection widget)
- определения специфичного права «Add» для содержимого
Код продукт может быть загружен по этой ссылке.
2.2. Структура пакета
Согласно принятым в Zope, Plone и AT соглашениям, содержимое нашего учебного продукта будет выглядеть следующим образом:
- __init__.py
- configure.zcml
- config.py
- interfaces.py
- content
- __init__.py
- message.py
- profiles
- default
- browser
- __init__.py
- configure.zcml
- instantmessage.pt
- tests
- __init__.py
- base.py
- test_setup.py
Для чего нужны эти файлы и папки?
- __init__.py: Обычная инициализация python модуля
- configure.zcml: Используя язык разметки конфигурации Zope (Zope Configuration Markup Language — ZCML), этот файл конфигурирует сервисы или поведение Zope сервера при старте
- config.py: хранит переменные конфигурации для продукта
- interfaces.py : в этом файле определяются интерфейсы описывающие классы пакета
- content: содержит модули обеспечивающие реализацию контент-типов. В нашем случае содержит файл message.py, в котором должен быть определен класс InstantMessage.
- profiles/default: Содержит набор XML файлов, которые необходимы для настройки и используются инструментом Plone Quick Installer, артифактом технологии Zope Generic Setup. Заметьте, что он пришел на смену старому способу основанному на Extensions/Install. Этот старый способ не используется в Plone 3.0 и выше.
- browser: подпакет, в который разработчики может добавить специальные части кода такие, как виды браузера и шаблоны, содержащий configure.zcml используемый для регистрации этих компонент.
- tests: содержит юнит-тесты продукта
Если у вас установлен ZopeSkel, вы можете использовать следующую комманду для создания подобной структуры пакета:
paster create -t archetype example.archetype
Теперь мы пройдемся по файлам и добавим в них то, что нужно для нашего приложения
2.3. Модуль interfaces
Модуль, в котором определяются интерфейсы пользовательских классов контента
Почему нужны интерфейсы?
Интерфейсы полезны для описания поведения класса. Это своего рода контракт между классом и компонентами, с которыми взаимодействует класс. Рекомендуется начинать разработку пакета с интерфейсов, так как это помогает документировать ваш код. В дополнение к вышесказанному, компонентная архитектура Zope (ZCA) позволяет использовать интерфейсы как компоненты для адаптации класса (что полезно, если требования к системе меняются со временем) и таким образом уточнять поведение класса.
Интерфейс класса InstantMessage
Сначала добавьте файл interfaces.py в корень пакета.
После чего нам нужно импортировать модель zope.interface (включен в поставку Zope версии 2.8 и выше):
from zope.interface import Interface
Следуя принятым в ZCA соглашениям для имен (имена интерфейсов начинаются с I), мы определим интерфейс IInstantMessage, который нужен для класса InstantMessage, определяемого позже:
class IInstantMessage(Interface):
"""
Interface for the InstantMessage class.
"""
И это все!
Мы могли бы добавить определения атрибутов, используя класс zope.interface.Attribute, но это необязательно. Интерфейс определенный выше, без функций или атрибутов, называется интерфейс-маркер (marker interface), что обозначает, что он используется только для маркировки экземпляров класса его реализующего.
Больше информации об интерфейсах можно получить из доктестов на Zope documentation site.
2.4. Модуль Configuration
Детали конфигурации вашего контент-типа, файл config.py
Сначала мы должны импортировать класс DisplayList из Архетипов:
from Products.Archetypes.atapi import DisplayList
DisplayList — это контейнер данных, которые используются для выпадающих списков, радиокнопок, чекбоксов. Скажем, мы хотим, чтобы сообщения были с приоритетом High, Normal и Low. Мы определим их позже в файле.
Следующие две строчки определяют имя проекта и директорию со скинами. Значением PROJECTNAME должно быть имя пакета: example.archetype.
PROJECTNAME = "example.archetype"
Теперь нам нужно определить значения для выпадающего списка Priority. Мы сделаем это, используя служебный класс, который предоставляют Архетипы:
MESSAGE_PRIORITIES = DisplayList((
('high', 'High Priority'),
('normal', 'Normal Priority'),
('low', 'Low Priority'),
))
Нам также необходимо определить право на добавление контент-типа (или несколько прав для разных контент-типов): :
ADD_CONTENT_PERMISSIONS = {
'InstantMessage': 'example.archetype: Add InstantMessage',
}
Рекомендуется использовать стандартный способ наименования прав: <имя продукта>: <имя права>. Это позволит группировать связанные права в списке прав в ZMI (вкладка Security) и администратору будет легче понять какое право какому продукту принадлежит.
Заметьте, что вам не нужно определять свои собственные права доступа на редактирование и просмотр контента, если у вас не сложный случай, который требует дополнительных настроек безопасности. В нашем простом случае мы будем просто использовать общие права доступа, определенные в CMFCore.permissions: "View", "Modify portal content"...
2.5. Модуль startup
Модуль startup
Перед запуском обычно кода инициализации Zope продукта, нам нужно определить Message Factory для интернационализации продукта:
from zope.i18nmessageid import MessageFactory
exampleMessageFactory = MessageFactory('example.archetype')
Определенный объект MessageFactory будет импортироваться в большинстве модулей со специальным именем "_" и инструменты интернациализации будут извлекать строки вида _(u"message") для перевода.
Далее мы импортируем полезные модули из API Архетиов: process_types нужен для получения контент-типов продукта, ассоциированных конструктурах и Factory Type Infromation (FTI), listTypes будет использоваться для получения списка типов доступных в продукте.
Нам также нужно импортировать модуль utils из CMFCore, чтобы позже иметь возможность использовать его класс ContentInit.
from Products.Archetypes.atapi import process_types from Products.Archetypes.atapi import listTypes from Products.CMFCore import utils
Замечания:
-
Factory Type Information (FTI): Часть конфигурации портала, структура данных, которая хранит информацию необходимую для описания контент-типа внутри портала. С другой сторона, FTI — это объект внутри компонента portal_types, который объясняет CMF и Plone как создавать контент этого типа и как его отображать.
-
Как работает listTypes?: обратите внимание на вызовы registerType() внутри модулей контент-типов. Заметьте, что мы также импортируем эти модули в content/__init__.py. Вызовы registerType говорят AT про тип, поэтому listTypes может найти их.
Один из важных шагов — импорт всего, что определено в подпакете content, то есть все его модули:
from content import message
Далее мы импортируем модуль конфигурации для того, чтобы иметь доступ к его переменным таким, как право доступ Add:
import config
Теперь время для реальных действий. Вы определяете функцию, которая нужна Zope и CMF для инициализации нашего контент-типа:
def initialize(context):
Первая часть функции генерирует контент-типы, конструктуры и информацию о типах (FTI) необходимую для того, чтобы ваши типы работали с CMF:
content_types, constructors, ftis = process_types(
listTypes(config.PROJECTNAME),
config.PROJECTNAME)
Вторая часть инициализрует объект класс ContentInit и регистрирует ваш тип в CMF:
utils.ContentInit(
"%s Content" % config.PROJECTNAME,
content_types = content_types,
permission = config.ADD_CONTENT_PERMISSIONS['InstantMessage'],
extra_constructors = constructors,
fti = ftis,
).initialize(context)
Обработка нескольких типов контента
Это пример кода для инициализации классов контент-типов с правом "Add" и конструктором, который работает, если у вас определено несколько типов контента. Он полезен, если вы планируете добавлять в продукт новые типы в будущем.
def initialize(context):
content_types, constructors, ftis = process_types(
listTypes(config.PROJECTNAME),
config.PROJECTNAME)
# We want to register each type with its own permission,
# this will afford us greater control during system
# configuration/deployment (credit : Ben Saller)
allTypes = zip(content_types, constructors)
for atype, constructor in allTypes:
kind = "%s: %s" % (config.PROJECTNAME, atype.portal_type)
utils.ContentInit(kind,
content_types = (atype,),
permission = config.ADD_CONTENT_PERMISSIONS[atype.portal_type],
extra_constructors = (constructor,),
fti = ftis,
).initialize(context)
Замечания:
-
Мы используем конструкцию "ADD_CONTENT_PERMISSIONS[atype.portal_type]", так как ADD_CONTENT_PERMISSIONS ссылается на словарь, в котором ключи - названия контент-типов.
-
Функция zip() - встроенная функция Python, которая создает список пар из элементов двух списков. В нашем случае, "allTypes" будет содержать список кортежей, где первый элемент кортежа - название типа, второй - его конструктор.
-
Если у вас несколько контент-типов, вы должны импортировать каждый модуль с классом контент-типа, как это сделано в примере с message выше!

