Персональные инструменты

Пример AT продукта

ПРИМЕЧАНИЕ: Это вид всех страниц руководства удобный для печати. Постраничное представление доступно здесь, если Вам так удобнее.

1. Вступление

В этой части руководства мы рассмотрим пример AT продукта, чтобы объяснить CMF/Archetypes на практике. Мы создадим продукт example.archetype, содержащий реализацию контент-типа InstantMessage — добавление сообщений пользователями с определенными правами для чтения другими пользователями. Однако, как вы можете догадаться, это будет скорее учебный пример, чем рабочий продукт для реального веб приложения.

Что такое продукт?

Продукт — Zope продукт, если быть точным — это сторонний аддон, который добавляет новую функиональность. Это пакет с кодом, написанным на Python  с соблюдением определенных договоренностей и правил.

Для понимания этого раздела вам понадобятся базовые знания работы с файловой системой и протоколов программирования  общих для Python и Zope.

example.archetype будет иллюстрировать следующие возможности CMF и Archetypes:

  • базовые поля и виджеты
  • определение и использования словаря для поля с виджетом выбора (selection widget)
  • определения специфичного права «Add» для содержимого

Код продукт может быть загружен по этой ссылке. 

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

Теперь мы пройдемся по файлам и добавим в них то, что нужно для нашего приложения

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.

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"...

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 выше!