Различия между версиями 37 и 38
Версия 37 от 2010-07-12 11:51:32
Размер: 97563
Редактор: RostislavDzinko
Комментарий:
Версия 38 от 2010-07-12 11:57:13
Размер: 107452
Редактор: RostislavDzinko
Комментарий:
Удаления помечены так. Добавления помечены так.
Строка 1017: Строка 1017:
= Запуск =

== Введение ==

The BlueBream framework creates WSGI applications, which can run behind any WSGI compliant web servers. The main application is configured and created via a factory function defined inside startup.py. This functions returns the WSGI complaint application object to the server. For example, in the “ticket collector” tutorial, you can see the factory function defined in src/tc/main/startup.py file:

import zope.app.wsgi

def application_factory(global_conf):
    zope_conf = global_conf['zope_conf']
    return zope.app.wsgi.getWSGIApplication(zope_conf)
PaseDeploy together with PasteScript are then used to run the WSGI application. However, any WSGI server can be used to run BlueBream application [1]. We provide PaseDeploy with the WSGI application factory as an entry point. For example, in the “ticket collector” tutorial, you can see the entry point defined in setup.py file:

[paste.app_factory]
main = tc.main.startup:application_factory
The application can now be launched as a web service using the paster serve command provided by PasteScript. To configure the web server, an INI file has to be passed to the command as an argument. The INI file defines the WSGI application, any WSGI middleware we may want and web server options. For example, in the “ticket collector” tutorial, you can see the WSGI application defined in deploy.ini file:

[app:main]
use = egg:ticketcollector

[server:main]
use = egg:Paste#http
host = 127.0.0.1
port = 8080

[DEFAULT]
# set the name of the zope.conf file
zope_conf = %(here)s/etc/zope.conf
You can read more about PasteDeploy and PasteScript in the PythonPaste site.

== Запуск WSGI приложения ==

When you run BlueBream application using the paster server command, you can see something like this:

$ ./bin/paster serve deploy.ini
...
Starting server in PID 13367.
serving on http://127.0.0.1:8080
[1] WSGI servers like mod_wsgi don’t require the paster serve command provided by PasteDeploy to run the WSGI server.

= Функциональное тестирование =

== Введение ==

In this chapter, you will learn more about functional testing. A doctest based package (zope.testbrowser) is used in BlueBream for functional testing. Unlike unit tests, functional tests are user interface (view) oriented.

== zope.testbrowser ==

The central part of this package is a browser object. This create this object, import Browser class from zope.testbrowser.testing:

>>> from zope.testbrowser.testing import Browser
>>> browser = Browser()
To learn the usage of package, first create a simple HTML page with following content:

<html>
  <head>
    <title>Test Page</title>
  </head>
  <body>
    <h1>Test Page</h1>
  </body>
</html>
To open a page:

>>> browser.open('http://localhost/zopetest/simple.html')
>>> browser.url
'http://localhost/zopetest/simple.html'

== Слой тестирования ==

== Запуск тестов ==

Your test suites can be placed in tests.py module under each packages. By default, in BlueBream there will be a tests.py with one test suite created using z3c.testsetup:

import z3c.testsetup

test_suite = z3c.testsetup.register_all_tests('tc.main')
The z3c.testsetup will aut-recover test suites from doctest files. You can create your doctest files, similar to example given in README.txt:

ticketcollector

:doctest:
:functional-zcml-layer: ftesting.zcml

Open browser and test::

  >>> from zope.testbrowser.testing import Browser
  >>> browser = Browser()
  >>> browser.open('http://localhost/@@index')
  >>> 'Welcome to BlueBream' in browser.contents
  True
The fouth line specifies that a ZCML file named ftesting.zcml is required to setup the test layer.

To run the tests:

$ ./bin/test

= Скины =

== Введение ==

It is often required to build web applications with equal/similar features, but different look and feel. Variation of look and feel can be simple or complex. It can be either just an exchange of a CSS file and some images. Sometimes, you will be required to reconfigure the application to have different user interface components, such as widgets, tables etc. Also you will be required to mix and match user interface components from multiple packages.

In BlueBream, the skin concept is implemented to use the Zope component architecture.

There are two terms associated with skinning named, layer and skin. Before proceeding, it would be better to understand the meaning of these two terms in BlueBream skinning. Skins are directly provided by a request

=== Слои ===

Define the “feel” of a site
Contain presentation logic
Common artifacts: pages, content providers, viewlet managers, and viewlets
Developed by BlueBream (Python) developers

=== Скины ===

Define the “look” of a site
Common artifacts: templates and resources (CSS, Javascript, etc.)
Use layers to retrieve the data for templates
Developed by HTML and Graphic Designer/Scripter

=== Слои против скинов ===

Both are implemented as interfaces
BlueBream does not differentiate between the two
In fact, the distinction of layers defining the “feel” and skins the “look” is a convention. You may not want to follow the convention, if it is too abstract for you, but if you are developing application with multiple look and feel, I strongly suggest using this convention, since it cleanly separates concerns.
Both support inheritance/acquisition
This is realized through a combination of interface inheritance and component lookup techniques. This book will discuss this in more detail later.

Unfortunately, it is hard to reuse the UI components developed for these skins, since they still rely on the not so flexible macro pattern. Thus, it is better if you start from scratch. This will also avoid a lot of the overhead that comes with the over-generalized core skins.

== Новый скин ==

Views registered for default layer by default zope.publisher.interfaces.browser.IDefaultBrowserLayer
Default layer contains a lot of things you do not need (security concerns)
Since pretty much everything in zope.app is registered into the default layer, it has an uncontrollable amount of junk in it. It is very hard to verify that all those registrations fulfill your security needs. Another problem is that views might be available that you do not want to be available.
Always want to develop skins from scratch
Some registrations in the default layer are very useful
Examples of those useful registrations include error views, traversal registrations, and widgets.

=== Установка слоя ===

Write an interface for the layer that inherits the minimal layer:

from zope.publisher.interfaces.browser import IBrowserSkinType

class IHelloWorldLayer(IBrowserSkinType):
    """Hello World Application Layer"""
Change all page, viewletmanager, and viewlet directives to specify this layer:

layer=".interfaces.IHelloWorldLayer"
Once you changed those registrations, the helloworld.html page will not be available anymore in the core skins. The templates by themselves do not matter.

=== Использование слоя ===

Registering views and resources is not any different now, but you can simply register them on the skin directly:

<browser:resource
    name="zope3logo.gif"
    file="images/zope3logo.gif"
    layer=".interfaces.IBasicSkin"
    />
As you can see, you don’t have to create an extra layer just to create a custom skin. You were also able to use standard Component Architecture ZCML directives instead of custom ones whose special syntax the developer needs to remember additionally.

A typical browser:page with with layer specified is like this:

<browser:page
    for="*"
    name="dialog_macros"
    permission="zope.View"
    layer=".interfaces.IBasicSkin"
    template="dialog_macros.pt"
    />

=== Установка скина ===

Skins are technically interfaces defined using zope.interface package. Write an interface for each new skin that inherits the Hello World application layer:

class IBasicSkin(IHelloWorldLayer):
    """Basic Skin for Hello World App."""
To register this you can use interface and utility directives in zope namespace. The type of the IShanghaiSkin skin is zope.publisher.interfaces.browser.IBrowserSkinType. Here is a sample configure.zcml:

<interface
    interface=".interfaces.IBasicSkin"
    type="zope.publisher.interfaces.browser.IBrowserSkinType"
    />

<utility
    component=".interfaces.IBasicSkin"
    provides="zope.publisher.interfaces.browser.IBrowserSkinType"
    name="BasicSkin"
    />
As a shortcut, you can also just use the interface directive and pass the new name parameter. The following one directive has the same effect as the two above regarding the skin registration:

<interface
    interface=".interfaces.IBasicSkin"
    type="zope.publisher.interfaces.browser.IBrowserSkinType"
    name="BasicSkin"
    />
Register all templates for this skin by adding the layer attribute:

layer=".interfaces.IBasicSkin"

=== Использование скина ===

Access it via: http://localhost:8080/++skin++BasicSkin

Hide skin traversal step by using Apache’s Virtual Hosting feature

To change the default skin to something else use:

<browser:defaultSkin name="BasicSkin" />
Simply specifying the browser:defaultSkin directive in your configuration file will not work, since it has been specified in zope/app/zcmlfiles/browser.zcml already. You can either change the skin at this location or use the zope:includeOverrides directive, which will override the any included directives.

== Итоги ==

This chapter introduced skinnig in BlueBream.

Руководство по BlueBream (часть 2)

Содержание

  1. Интерфейсы (Interface)
    1. Введение
    2. Определение интерфейсов
    3. Пример
    4. Объявление интерфейсов
    5. Реализация интерфейсов
    6. Интерфейсы-маркеры
  2. Контент компоненты (Content component)
    1. Введение
  3. Схемы и виджеты
    1. Введение
    2. Схема против интерфейса
    3. Основные поля схемы
      1. Свойства, которые поддерживают все поля
      2. Bytes, BytesLine
      3. Text, TextLine
      4. SourceText
      5. Password
      6. Bool
      7. Int
      8. Float
      9. Datetime
      10. Tuple, List
      11. Dict
      12. Choice
      13. Object
      14. DottedName
      15. URI
      16. Id
      17. InterfaceField
    4. Автоматическая генерация форм с помощью пакета forms
      1. Текстовые виджеты
      2. Логические виджеты
      3. Виджеты единичного выбора
      4. Виджеты множественного выбора
      5. Другие виджеты
  4. Формы
    1. Введение
    2. Концепции
      1. Виджеты
      2. Действие (Action)
    3. Создание формы
    4. Создание формы редактирования (EditForm)
    5. Выводы
  5. Юнит тестирование (Unit testing)
    1. Введение
    2. Запуск тестов
  6. Компонентная архитектура Zope
    1. Введение
    2. Адаптеры
      1. Реализация
      2. Регистрация
      3. Получение адаптера
      4. Получения адаптера по интерфейсу
    3. Утилита
      1. Простая утилита
      2. Именованная утилита
  7. Конфигурация
    1. Введение
  8. Запуск
    1. Введение
    2. Запуск WSGI приложения
  9. Функциональное тестирование
    1. Введение
    2. zope.testbrowser
    3. Слой тестирования
    4. Запуск тестов
  10. Скины
    1. Введение
      1. Слои
      2. Скины
      3. Слои против скинов
    2. Новый скин
      1. Установка слоя
      2. Использование слоя
      3. Установка скина
      4. Использование скина
    3. Итоги

Интерфейсы (Interface)

Введение

Интерфейсы - это объекты, которые определяют (документируют) внешнее поведение объектов, которые их предоставляют. Интерфейс определяет поведение через:

  • Информационное документирование в строках документации (doc string)
  • Определение атрибутов
  • Инварианты, являющиеся условиями, которые должны выполнятся для объектов, которые предоставляют интерфейс.

Вот несколько мотиваций для использования интерфейсов:

  • Избежание монолитной архитектуры, разрабатывая маленькие, заменяемые части приложения;
  • Моделирование внешней ответственности, функциональности и поведения
  • Установка связей между частями функционала
  • Документирование API.

Классическая книга по программной инженерии Design Patterns, авторами которой являются Банда четырех рекомендует программировать на интерфейс, а не на реализацию. Определение формального интерфейса помогает в понимании системы. Более того, интерфейсы приносят все преимущества компонентной архитектуры Zope.

В некоторых современных языках программирования: Java, C#, VB.NET и т.д., интерфейсы - явный аспект языка. Поскольку в Python интерфейсов нет, Zope реализует их через механизм мета-классов, от которых необходимо наследоваться.

Я могу сделать X

  • Описание способности что-нибудь сделать - классическое определение API. Эти способности определены и реализованы как методы.

У меня есть X

  • Это высказывание объявляет о доступности данных, которые классически ассоциируются со схемами. Данные хранятся в атрибутах и свойствах.

Вы можете сделать X используя меня

  • Тут мы описываем поведение объекта. Классического аналога нет, но MIME-типы - замечательный пример объявления поведения. Это реализуется с помощью интерфейсов-маркеров (marker interfaces), так как они неявно описывают поведение. Разницу между этими тремя типами взаимодействия впервые указал Philipp von Weitershausen.

Понимание различий этих трех подходов очень важна, так как другие языки программирования не обязательно их реализуют. Обычно используется только первый.

Определение интерфейсов

  • Python не реализует концепцию интерфейсов
  • Это не проблема
  • Интерфейсы - всего лишь объекты
  • “Злоупотребляют” ключевым словом class для создания интерфейса

  • Синтаксис был предложен в PEP 245

В Java, например, интерфейсы - специальные типы объектов, которые могут служить в качестве интерфейсов в их ограниченной области видимости.

С другой стороны интерфейс из пакета zope.interface определяет интерфейс путем реализации мета-класса, одной из основополагающих концепций языка Python. Тем не менее, интерфейс просто используют существующий шаблон Python.

Пример

Вот классический пример в стиле hello world:

   1 >>> class Host(object):
   2 ...
   3 ...     def goodmorning(self, name):
   4 ...         """Say good morning to guests"""
   5 ...
   6 ...         return "Good morning, %s!" % name

В этом классе вы определили метод goodmorning. Если вы вызываете этот метод из экземпляра этого класса, он вернет Good morning, ...!.

   1 >>> host = Host()
   2 >>> host.goodmorning('Jack')
   3 'Good morning, Jack!'

Здесь host - экземпляр класса (объект), который использует код приведенный выше. Если вам нужны детали реализации, вам следует обратится к классу Host, либо через исходный код, либо через API документации.

Теперь будем использовать интерфейсы. Для данного класса определим интерфейс следующим образом:

   1 >>> from zope.interface import Interface
   2 
   3 >>> class IHost(Interface):
   4 ...
   5 ...     def goodmorning(guest):
   6 ...         """Say good morning to guest"""

Как видите, интерфейс является потомком класса zope.interface.Interface. Префикс I - очень полезная конвенция для именования интерфейсов.

Объявление интерфейсов

В предыдущей главе Вы уже увидели как объявлять интерфейс с использованием пакета zope.interface. Эта глава объясняет технологию интерфейсов более подробно.

Возьмем в качестве примера следующее:

   1 >>> from zope.interface import Interface
   2 >>> from zope.interface import Attribute
   3 
   4 >>> class IHost(Interface):
   5 ...     """A host object"""
   6 ...
   7 ...     name = Attribute("""Name of host""")
   8 ...
   9 ...     def goodmorning(guest):
  10 ...         """Say good morning to guest"""

Интерфейс IHost обладает двумя атрибутами: name и goodmorning. Вспомните что, по крайней мере в Python, методы также являются атрибутами классов. Атрибут name определен с использованием класса zope.interface.Attribute. Когда вы добавляете атрибуту name к интерфейсу IHost, то не устанавливаете начальное значение. Здесь цель определения этого атрибута - просто показать, что любая реализация этого интерфейса будет содержать атрибут name. В этом случае, мы даже не указали тип атрибута! Вы можете передать строку документации в качестве первого параметра конструктору Attribute.

ругой атрибут, goodmorning - это метод, который определен с использованием синтаксиса функций. Заметьте, что self в этом случае не обязателен, потому что детали реализации - это уже класс. Например, модуль может реализовать интерфейс. Если модуль реализует этот интерфейс, он будет обладать атрибутом name и содержать определенную функцию goodmorning. Функция goodmorning будет принимать один аргумент.

Сейчас мы осветим связку интерфейс-класс-объект. Объект - это "реальная живая" вещь, и объекты являются экземплярами классом. Интерфейс - действительное формальное описание объекта, соответственно классы - просто реализация интерфейса. Вот почему следует программировать интерфейсы, а не реализации.

Теперь вам следует ознакомится еще с двумя понятиями объединяющими эти концепции. Первое - предоставлять (provide), а второе - реализовать (implement). Объекты предоставляют интерфейсы, а классы реализуют интерфейсы. Другими словами, объекты предоставляют интерфейсы, которые реализуют их классы. В предыдущем примере host (объект) предоставляет IHost (интерфейс), а Host (класс) реализует IHost (интерфейс). Один объект может предоставлять более одного интерфейса; также один класс может реализовать более одного интерфейса. Объекты также могут предоставлять интерфейсы прямо, вдобавок к тому, что реализуют их классы.

Классы - реализация объектов. В Python, классы - вызываемые объекты, так почему бы другим вызываемым объектам не реализовать интерфейс? Да, это возможно. Вы можете указать любому вызываемому объекту, что он создает объекты, предоставляющие некие интерфейсы, говоря что этот вызываемый объект реализует интерфейсы. Вызываемые объекты в общем случае называются фабриками (factories). Так как функции - вызываемые объекты, они могут реализовать интерфейсы.

Реализация интерфейсов

Для того, чтобы объявить о том, что класс реализует некий интерфейс, используйте функцию zope.interface.implements в коде определения класса.

Представьте себе пример, в котором Host реализует IHost:

   1 >>> from zope.interface import implements
   2 
   3 >>> class Host(object):
   4 ...
   5 ...     implements(IHost)
   6 ...
   7 ...     name = u''
   8 ...
   9 ...     def goodmorning(self, guest):
  10 ...         """Say good morning to guest"""
  11 ...
  12 ...         return "Good morning, %s!" % guest

Если вам интересно, как работает функция implements, обратитесь к блогу James Henstridge (http://blogs.gnome.org/jamesh/2005/09/08/python-class-advisors/). В разделе адаптеры, вы найдете функцию adapts, которая работает таким же образом.

Так как Host реализует IHost, экземпляры класса Host предоставляют IHost. Существуют также вспомогательные методы для просмотра деклараций. Декларация также может быть описана вне класса. Если вы не сделали interface.implements(IHost), как показано в примере выше, то после определения класса, вы можете сделать следующее:

   1 >>> from zope.interface import classImplements
   2 >>> classImplements(Host, IHost)

Интерфейсы-маркеры

Интерфейсы могут использоваться для того, чтобы объявить о том, что объект принадлежит к некоему специальному типу. Интерфейс без каких-либо атрибутов и методов называют маркером.

Вот пример интерфейса-маркера:

   1 >>> from zope.interface import Interface
   2 
   3 >>> class ISpecialGuest(Interface):
   4 ...     """A special guest"""

Этот интерфейс может использоваться для того, чтобы объявить о том, что объект является специальным гостем.

Контент компоненты (Content component)

Введение

Взгляните на следующий пример:

   1 >>> from zope import interface
   2 
   3 >>> class IPerson(interface.Interface):
   4 ...     name = interface.Attribute("Name")
   5 >>> class Person(object):
   6 ...     interface.implements(IPerson)
   7 ...     name = None
   8 >>> jack = Person()
   9 >>> jack.name = "Jack"

В этом примере jack - это контент-компонент. Следовательно, контент-компонент - не что иное, как объект, который предоставляет определенный интерфейс. Как было сказано в предыдущей главе, можно использовать zope.schema для определения полей интерфейса. В предыдущем интерфейсе, их можно определить примерно так:

   1 >>> from zope import interface
   2 >>> from zope import schema
   3 
   4 >>> class IPerson(interface.Interface):
   5 ...     name = schema.TextLine(
   6 ...         title=u"Name",
   7 ...         description=u"Name of person",
   8 ...         default=u"",
   9 ...         required=True)

Если вы разрабатываете корпоративное приложение, содержимое (контент) - это одна из наиболее важных вещей, которую следует организовать в первую очередь. Для того, чтобы обучить вас писать приложения с контент-компонентами, эта глава содержит пример простого приложения сборщика заявок.

Сначала рассмотрим желание клиента, которое будет реализовано в следующих главах.

  1. Один контейнер заявок для каждого проекта. Много контейнеров могут быть добавлены в проект.
  2. В контейнер может быть добавлено любое количество заявок.
  3. Каждая заявка будет добавлена с описанием и одним начальным комментарием.
  4. Следующие комментарии могут быть добавлены к заявкам.
  5. Эта глава начинает простую реализацию сборщика заявок.

Как говорилось выше, наша цель - разработать полностью рабочий, хотя необязательно красиво выглядящий, веб ориентированный сборщик заявок. Корневым объектов будет Collector, который может содержать объекты заявок (Ticket) от различных пользователей. Так как вы хотите разрешить пользователям отвечать на заявки, вам следует предоставить заявкам возможность хранить в себе ответы, которые и есть объектами Comment.

Это значить, что у вас есть два компонента контейнера: Collector содержит только заявки, и может быть добавлен в любую папку Folder или другой контейнер, который способен содержать его. Для того, чтобы сделать сборщик заявок поинтереснее, он также должен иметь описание, которое вкратце представляет предмет/тему дискуссии. Заявки, с другой стороны могут хранится только в объекте Collector. Каждая из них будет иметь краткое и развернутое описание. И напоследок, Comment должен хранится только в заявках.

Такая настройка должна содержать все необходимые вещи, которые сделают проект полезным. Позже вы добавите еще много мета данных к этим компонентам для реализации более плотной интеграции с BlueBream и добавления дополнительного функционала.

Наиболее удобное место для расположения пакета - $HOME/myzope/lib/python. Для создания пакета, добавьте папку:

   1 $ cd $HOME/myzope/lib/python/
   2 $ mkdir collector

(на GNU/Linux).

Для того, чтобы сделать эту папку пакетом, добавьте туда пустой файл init.py. В GNU/Linux вы можете сделать примерно следующее:

   1 $ echo "# Make it a Python package" >> collector/__init__.py

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

Теперь вы будете работать только внутри пакета collector, который размещен в $HOME/myzope/lib/python/collector.

Схемы и виджеты

Введение

На ранних этапах разработки, разработчики BlueBream (раньше Zope 3) решили, что писать HTML формы вручную и вручную проверять правильность введенных данных довольно громоздко. Команда BlueBream поняла, что если расширить интерфейсы, можно будет автоматически генерировать формы и выполнять проверку введенных данных. Эта глава предоставляет информацию и формально представляет пакеты zope.schema и zope.formlib.

Schema - это интерфейс, который определен с помощью специальных атрибутов, называемых "полями".

Схема разрабатывалась для достижения трех основных целей:

  • Полное описание свойств на уровне API
  • Проверка и конверсия пользовательского ввода
  • Автоматизированная генерация GUI форм (в основном для Web обозевателя)

Схема против интерфейса

Как упоминалось выше, схемы - это просто расширения интерфейсов и поэтому они зависят от пакета zope.interface. Поля схем равноценны методам в интерфейсах. Они совместимы друг с другом, так как описывают разные аспекты объекта. Методы интерфейса описывают функциональность компонента, тогда как поля схемы представляют состояние.

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

   1 from zope.interface import Interface
   2 from zope.schema import Text
   3 
   4 class IExample(Interface):
   5 
   6    text = Text(
   7        title=u"Text",
   8        description=u"The text of the example.",
   9        required=True)
  • Строка 2: все стандартные поля импортируются из zope.schema.

  • Строки 7-8: заголовок и описание используют человеческий язык (это нужно для генерации форм). Они также служат в качестве документации к полю.
  • Строка 9: разные поля поддерживают несколько полей мета данных. Настройка required доступна для всех полей и определяет, является ли поле обязательным, или нет (то есть может ли значение поля быть пустым).

Основные поля схемы

После того, как мы увидели простой пример схемы, давайте рассмотрим все основные поля и их свойства.

Свойства, которые поддерживают все поля

  • title (тип: TextLine): заголовок атрибута, используется как метка при отображении виджета поля.

  • description (тип: Text): описание атрибута, используется во всплывающих подсказках и расширенной помощи.

  • required (тип: Bool): определяет, является ли атрибут обязательным (то есть, может ли быть его значение пустым). В формах добавления, обязательные атрибуты равнозначны обязательным аргументам конструктора.

  • readonly (тип: Bool): если поле readonly, то значение атрибута устанавливается один раз, и после этого не изменяется, то есть доступно только для отображения. Часто уникальный идентификатор для некоего объекта используется как поле только для чтения.

  • default (тип: зависит от поля): значение по умолчанию, которое дается атрибуту, если не была проведена инициализация. Это значение часто определяется для обязательных полей.

  • order (тип: Int): поля часто группируются в некоем логическом порядке. Это значение определяет относительную позицию в этом порядке. Обычно это значение не устанавливается вручную, так как оно автоматически присваивается при инициализации интерфейса. Порядок полей в схеме по умолчанию такой же, как и в коде Python.

Bytes, BytesLine

Bytes и BytesLine отличаются только тем, что BytesLine не может содержать символ новой строки. Bytes ведут себя так же, как и строка в Python.

Поля Bytes и BytesLine доступны для выполнения итераций.

  • min_length (тип: Int): минимальное количество символов в строке. По умолчанию - None, что значит, что ограничения нет.

  • max_length (тип: Int): максимальное количество символов в строке. По умолчанию - None, что значит, что ограничения по максимуму нет.

Text, TextLine

Два этих поля отличаются только тем, что TextLine не может содержать символ новой строки. Текстовые поля содержат юникод, а это значит, что они предназначены для хранения текстов, которые будут читать пользователи приложение.

Поля Text и TextLine доступны для выполнения итераций.

  • min_length (тип: Int): минимальное количество символов в строке. По умолчанию - None, что значит, что ограничения нет.

  • max_length (тип: Int): максимальное количество символов в строке. По умолчанию - None, что значит, что ограничения по максимуму нет.

SourceText

SourceText - специальное поле, наследуемое от Text, которое содержит исходный код любого типа. Это поле более менее похоже на маркер для того, чтобы указать механизму создания форм, что нужны специальные поля ввода для исходников.

Password

Password - специальный дочерний класс поля TextLine, создан только в целях отделения механизмов отображения. Также для паролей обычно используется специальная проверка вводимых данных.

Bool

Поле Bool не имеет атрибутов. Оно отображает значение прямо в тип данных bool языка Pytho.

Int

Поля Int прямо отображаются в целочисленный тип языка Python.

  • min (тип: Int): описывает минимально допустимое значение. Это удобно во многих случаях, например разрешая только положительные значения, предоставляя этому параметру значение 0.

  • max (тип: Int): описывает наибольшее допустимое целочисленное значение поля, исключая себя. Оно может быть использовано для установки верхней границы, например, года, если вы заинтересованы только в прошлом.

Оба атрибута могут использоватся вместе, чтобы позволить разработчику устанавливать принимаемые значения.

Float

Поле Float прямо отображается в тип данных float языка Python.

  • min (тип: Int): описывает минимально допустимое значение. Это удобно во многих случаях, например разрешая только положительные значения, предоставляя этому параметру значение 0.

  • max (тип: Int): описывает наибольшее допустимое целочисленное значение поля, исключая себя. Оно может быть использовано для установки верхней границы, например, года, если вы заинтересованы только в прошлом.

Оба атрибута могут использоватся вместе, чтобы позволить разработчику устанавливать принимаемые значения.

Datetime

Похожее на Int и Float, Datetime, имеет поля min и max, определяет границы возможных значений. Принимаемые значения этих полей должны быть экземплярами встроенного типа данных datetime.

Tuple, List

Причина, по которой эти поля существуют такова, что они просто отображаются в родные типы данных Python - tuple и list, соответственно.

Поля Tuple и List доступны для итераций.

  • min_length (тип: Int): в последовательности не может быть меньше элементов, чем это значние. По умолчанию - None, который значит что минимального количества нет.

  • max_length (тип: Int): не может быть большего количество элементов в последовательности. По умолчанию - None, который значит, что максимума не существует.

  • value_type (тип: Field): значения, которые содержатся в этой последовательности, должны соответствовать ограничению поля. Наиболее общий случай - поле Choice (смотрите ниже), которое позволяет выбрать из смешанного набора значений.

Dict

Dict - поле, что отображает множество значений на другое множество значений.

Это поле доступна для выполнения итераций.

  • min_length (тип: Int): в последовательности не может быть меньше элементов, чем это значние. По умолчанию - None, который значит что минимального количества нет.

  • max_length (тип: Int): не может быть большего количество элементов в последовательности. По умолчанию - None, который значит, что максимума не существует.

  • key_type (тип: Field): каждый ключ словаря должен соответствовать указанному полю.

  • value_type (тип: Field): значения, которые содержатся в этой последовательности, должны соответствовать ограничению поля. Наиболее общий случай - поле Choice (смотрите ниже), которое позволяет выбрать из смешанного набора значений.

Choice

Поле Choice позволяет выбрать конкретное значение из предостваленного списка. Вы можете предоставлять значения как простую последовательность (список или кортеж), либо указать словарь (vocabulary) (по ссылке или имени), который предоставляет эти значения. Словари предоставляют гибкий список значений, другими словами - множество допустимых значений меняется с изменениями в системе. Так как они более сложные, они описаны отдельно в "Словари и Поля".

  • vocabulary (тип: Vocabulary): экземпляр словаря, который предоставляет доступные значения. Этот атрибут - None, если имя словаря указано, а поле не привязано к контексту.

  • vocabularyName (тип: TextLine): имя словаря, который предоставляет значения. Словарь по этому имени ищется, когда поле привязано, другими словами, - имеет контекст. Насчет привязки: словарь по умолчанию ищется по имени и контексту.

Конструктор также принимает аргумент values, который определяет статическое множество значений. Эти значения сразу же конвертируются в статический словарь (vocabulary).

Object

Это поле определяет объект, который должен реализовать конкретную схему. Допускаются только объекты, которые реализуют схему.

  • schema (тип: Interface): это поле предоставляет ссылку на схему, которую должны предоставлять объекты, которые хранятся в описываемом атрибуте.

DottedName

Наследовано от поля BytesLine, поле DottedName представляет правильные имена в стиле Python (ссылки на объекты). Это поле может быть использовано, когда нужно предоставить правильное имя в стиле Python.

Поле не имеет дополнительных атрибутов.

URI

Наследовано от поля BytesLine, поле URI уверяет, что значение - всегда правильный URI. Это особенно полезно, когда нужно хранить ссылки на ресурсы (такие как RSS ленты или изображения) на удаленных компьютерах.

Это поле не имеет дополнительных атрибутов.

Id

Вместе поля DottedName и !URI, создают поле Id. Любое имя или URI представляют правильный идентификатор в Zope. Идентификаторы используются для определения многих типов объектов, таких как права доступа и пользователи, но также используются для предоставления ключей аннотаций.

Это поле не имеет дополнительных атрибутов.

InterfaceField

Поле Interface не имеет дополнительных атрибутов. Это значение - это объект, который предоставляет zope.interface.Interface, другими словами - это должен быть интерфейс.

Для получения полного списка API схем/полей посмотрите инструмент документации по API по ссылке http://localhost:8080/++apidoc++ или модуль zope.schema.interfaces.

Автоматическая генерация форм с помощью пакета forms

Формы - более Zope-специфические, чем схемы, и находятся в пакете zope.formlib. Виды для полей схемы называются виджетами. Виджеты отвечают за отображение данных и их конвертацию в специальный тип данных для представления. На данный момент существуют виджеты в основном для HTML (веб обозревателя).

Виджеты разделены на две группы: отображения и ввода. Виджеты отображения зачастую очень простые и показывают только текстовое представление Python объекта. Виджеты ввода более сложные и имеют более широкий выбор. Следующий список показывает все доступные в обозревателе виджеты ввода (находятся в zope.formlib.widget):

Текстовые виджеты

Текстовые виджеты всегда требуют ввода с клавиатуры. Строковое представление поля потом конвертируется в нужный Python объект, например integer или date.

  • TextWidget: будучи самым простым виджетом, он отображает строку ввода, и, в основном, используется для поля TextLine, которое ожидает ввода юникод строки. Он также служит базовым виджетом для многих последующих.

  • TextAreaWidget: как и предполагает имя, виджет отображает поле для ввода текста, и ожидает некую юникод строку. (заметьте, что Publisher сам заботится о кодировании/декодировании).

  • BytesWidget, BytesAreaWidget: прямые потомки TextWidget и TextAreaWidget, разница только в том, что эти виджеты принимают на ввод байтовую, а не юникод строку, что значит, она должна быть ASCII кодируемая.

  • ASCIIWidget: этот виджет базируется на BytesWidget, и уверяет, что вводными данными являются только ASCII символы.

  • PasswordWidget: Почти идентичные TextWidget, только отображает вместо строки ввода текста строку ввода пароля.

  • IntWidget: наследуется от TextWidget, переопределяет метод конвертации, чтобы убедится, что вводимые данные - целое число.

  • FloatWidget: наследуется от TextWidget, переопределяет метод конвертации, чтобы удостоверится, что вводимые данные - число с плавающей точкой.

  • DatetimeWidget: кто-то может ожидать сложный виджет здесь, но пока - это просто TextWidget, в котором происходит конвертация строки в тип datetime. Также существует DateWidget, который обрабатывает только даты.

Логические виджеты

Ответственность логических виджетов заключается в том, чтобы сконвертировать некий двоичный ввод в значения True или False.

  • CheckBoxWidget: этот виджет отображает одну галочку (checkbox), которая может иметь состояния "отмечено" или "не отмечено", отображая состояния логического значения.

  • BooleanRadioWidget: две кнопки опции, которые используются для отображения состояния истина/ложь логической переменной. Для двух состояний в конструктор нужно передавать текстовые значения. По умолчанию - “on” и “off” (или их перевод для конкретного языка).

  • BooleanSelectWidget, BooleanDropdownWidget: Подобно BooleanRadioWidget, текстовые отображения состояний, которые используются для установки значения. Посмотрите SelectWidget и DropdownWidget, соответственно, для получения более полной информации.

Виджеты единичного выбора

Виджеты, которые позволяют выбрать одно значение из списка, обычно являются видами для поля, словаря (vocabulary) и запроса (request), вместо пары "поле" и "запрос", поэтому используются прокси-виджеты, которые отображают "поле-запрос" в "поле-словарь-запрос". Например, ChoiceInputWidget, который принимает поле Choice и объект запроса, - это просто функция, которая ищет другой виджет, который зарегистрировать на поле Choice, его словарь и запрос. Ниже приведен список всех доступных виджетов выбора одного значения.

  • SelectWidget: этот виджет предоставляет элемент управления выбора, где значения создаются через элементы словаря. Если поле не обязательное, будет доступна опция “нет значения”. Пользователю разрешается выбрать только одно значение, так как поле Choice - поле, не основанное на последовательности.

  • DropdownWidget: простой наследник SelectWdiget, его размер установлен в “1”, что делает его выпадающем списком. Выпадающие списки имеют одно преимущество - они всегда показывают только одно значение, что делает их более дружественными для единичного выбора.

  • RadioWidget: этот виджет отображает кнопку опцию для каждого элемента словаря. Преимущество таких опций в том, что они всегда отображают все возможные варианты выбора, поэтому хорошо подходят для небольшого набора значений (маленьких словарей).

Виджеты множественного выбора

Эта группа виджетов используется для отображения форм ввода полей, основанных на коллекциях, таких как List или Set. Подобно виджетам единичного выбора, здесь используются прокси-виджеты с целью поиска правильного виджета. Первый шаг - отображение "поле-запрос" в "поле- тип значения-запрос", используя виджет CollectionInputWidget. Этот шаг позволяет использовать разные виджеты, когда, например, тип значения - поле "Int" или "Choice". Другой (не обязательный) прокси-виджет используется для отображения "поле-тип значения-запрос" в "поле-словарь-запрос", в том случае, если тип значения - поле выбора.

  • MultiSelectWidget: создает элемент выбора с атрибутом multiple установленным в "true". Так создается список с множественным выбором. Это особенно полезно для словарей с большим набором элементов. Заметьте, если вам словарь поддерживает интерфейс запросов (query interface), вы можете фильтровать доступные значения.

  • MultiCheckBoxWidget: подобно предыдущему, этот виджет позволяет делать множественный выбор из списка значений, но использует галочки вместо списка. Этот виджет более полезен для маленьких словарей.

  • TupleSequenceWidget: этот виджет используется для всех случаев, при которых тип значения - не поле Choice. Он использует виджет типа данных значения для добавления новых элементов в кортеж. Другие элементы управления используются для удаления элементов.

  • ListSequenceWidget: этот виджет равнозначен предыдущему, кроме того, что создает список вместо кортежа.

Другие виджеты

  • FileWidget: этот виджет отображает элемент для загрузки файла, и убеждается в том, что полученные данные - действительно файл. Это поле идеально для загрузки потоков байт, необходимых для поля Bytes.

  • ObjectWidget: ObjectWidget - это вид для поля объекта. Он использует схему объекта для построения формы ввода. Фабрика объекта, которая передается как аргумент конструктору, используется потом для построения объекта из вводимых данных.

Вот простой интерактивный пример, который показывает отрисовку и конвертацию данных виджета:

   1 >>> from zope.publisher.browser import TestRequest
   2 >>> from zope.schema import Int
   3 >>> from zope.formlib.widget import IntWidget
   4 >>> field = Int(__name__='number', title=u'Number', min=0, max=10)
   5 >>> request = TestRequest(form={'field.number': u'9'})
   6 >>> widget = IntWidget(field, request)
   7 >>> widget.hasInput()
   8 True
   9 >>> widget.getInputValue()
  10 >>> print widget().replace(' ', '\n  ')
  11 <input
  12    class="textType"
  13    id="field.number"
  14    name="field.number"
  15    size="10"
  16    type="text"
  17    value="9"
  18 
  19 />
  • Строки 1 & 5: для видов, включая виджеты, всегда необходим объект запроса. Класс TestRequest - быстрый и простой путь создания запроса без особых хлопот. Для каждого типа отображения существует класс TestRequest. Класс получает аргумент form, который является словарем (dictionary) значений, содержащихся в HTML форме. Позже виджет получит доступ к этой информации.

  • Строка 2: импорт целочисленного поля.
  • Строки 3 & 6: импорт виджета, который отображает и конвертирует целое число из ввода в HTML форме. Инициализация виджета требует только поле и запрос.

  • Строка 4: создает целочисленное поле с ограничением диапазона значений [0 - 10]. Аргумент _ _name_ _ должен передаваться, так как поле не было инициализировано внутри интерфейса, в котором _ _name присваивается автоматически.

  • Строки 7-8: этот метод проверяет, содержала ли форма значение для этого виджета.
  • Строки 9-10: если да, мы можем использовать метод getInputValue() для возвращения сконвертированного и проверенного значения (в данном случае - целое число). Если бы мы ввели целое число вне заданного диапазона, то получили бы ошибку WidgetInputError.

  • Строки 11-20: Отображаем HTML представление виджета. Вызов replace() осуществляется только для повышения читаемости вывода.

Заметьте, что вы, обычно, не будете иметь дело с этими методами напрямую вообще, так как генератор форм и конвертер данных делают всю работу за вас. Единственный метод, который обычно переопределяется - _validate(), который используется для проверки ввода пользовательских значений. Это ведет нас к следующей теме - пользовательские виджеты.

Есть два способа создавать пользовательские виджеты. Для небольших настроек параметров (свойств виджета), можно использовать поддирективу browser:widget директив browser:addform и browser:editform. Например, чтобы поменять виджет для поля name, можно использовать следующий ZCML код.

   1 <browser:addform
   2   ... >
   3 
   4   <browser:widget
   5       field="name"
   6       class="zope.formlib.widget.TextWidget"
   7       displayWidth="45"
   8       style="width: 100%"/>
   9 
  10 </browser:addform>

В этом случае мы заставляет систему использовать TextWidget для "name", устанавливаем ширину в 45 символов и добавляем атрибут "style", который пытается установить ширину поля ввода на максимально доступную.

Вторая возможность поменять виджет для поля - создать свой класс вида. Здесь, пользовательские виджеты легко реализуемые с помощью класса обертки CustomWidget. Вот вводный пример:

   1 from zope.formlib.widget import CustomWidget
   2 from zope.formlib.widget import TextWidget
   3 
   4 class CustomTextWidget(TextWidget):
   5     ...
   6 
   7 class SomeView:
   8     name_widget = CustomWidget(CustomTextWidget)
  • Строка 1: так как CustomWidget - это представление, независимое от типа данных, он определен в zope.app.form.widget.

  • Строки 4-5: вы просто расширяете существующий виджет. Здесь вы можете переопределить все, включая метод _validate().

  • Строки 7-8: вы можете заполучить пользовательский виджет добавляя ему атрибут name_widget, где name - имя поля. Значение атрибута - экземпляр класса CustomWidget. Конструктор CustomWidget имеет только один обязательный аргумент, который является пользовательским виджетом для поля. Другие ключевые аргументы могут быть также указаны, и предназначены для установки атрибутов виджета.

Более подробную информацию о схемах можно получить в файле README.txt пакета zope.schema. Сайт разработчиков Zope 3 также содержит немного дополнительного материала.

Это завершение ввода в схемы и формы. Для получения примеров по схемам и формам на практике, просмотрите первые главы раздела "Контент-компоненты - Основы".

Формы

Введение

BlueBream обладает библиотекой HTML форм - zope.formlib, предназначенной для генерации форм и виджетов. Вместо использования библиотеки, вы также можете создать форму вручную. Но formlib избегает большого количества рутинной и повторяющейся работы. formlib генерирует формы для получения пользовательского ввода. Вы также можете создавать проверки ввода и ответы.

Формы - это веб компоненты, которые используют виджеты для отображения и ввода данных. Обычно шаблон отображает виджеты, получая доступ к атрибуту или методу низлежащего класса. Библиотека form поддерживает проверку пользовательского ввода. Библиотека автоматически ковертирует отправленные пользователем данные форм в объекты языка Python.

Библиотека formlib предоставляет базовые классы для создания классов видов. Наиболее используемыми базовыми классами являются DisplayForm, AddForm и EditForm. DisplayForm, на самом деле, не является веб формой, которую можно заполнить и отправить, а просто удобным способом отображения значений, которые основаны на конкретном контексте/интерфейсе.

Есть еще другое сообщество, поддерживающее библиотеку z3c.form. Многие проекты используют эту библиотеку, также она хорошо задокументирована.

Концепции

Перед продвижением дальше давайте рассмотрим основополагающие концепции форм.

Виджеты

Formlib определяет виджет следующим образом: “виды привязанные к полям схемы”

=== Поля ==

Поля строятся на полях схемы.

=== Форма ==

Класс формы может определить упорядоченную коллекцию "полей формы" используя конструктор Fields. Поля формы отличаются и строятся на полях схемы. Поле схемы определяет значения атрибутов. Поля формы определяют то, как поле схемы используется в форме. Самый простой путь определить коллекцию полей формы - передать схему в конструктор Fields.

Действие (Action)

Создание формы

AddForm может использоваться как базовый класс для видов. Проимпортировать можно следующим образом:

from zope.formlib.form import AddForm A typical registration of view can be done like this:

   1 <browser:page
   2    for="zope.site.interfaces.IRootFolder"
   3    name="add_sample_app"
   4    permission="zope.ManageContent"
   5    class=".views.AddSampleApplication"
   6    />

Вам нужно определение схемы, как объяснено в предыдущей главе:

   1 class AddSampleApplication(form.AddForm):
   2 
   3     form_fields = form.Fields(ISampleApplication)
   4 
   5     def createAndAdd(self, data):
   6         name = data['name']
   7         description = data.get('description')
   8         namechooser = INameChooser(self.context)
   9         app = SampleApplication()
  10         name = namechooser.chooseName(name, app)
  11         app.name = name
  12         app.description = description
  13         self.context[name] = app
  14         self.request.response.redirect(name)

Создание формы редактирования (EditForm)

Использование EditForm очень похоже на использование AddForm.

Выводы

Этот раздел продемонстрировал библиотеку zope.formlib, которая используется для генерации HTML форм и виджетов.

Юнит тестирование (Unit testing)

Введение

Этот раздел обсуждает юнит тестирование и интегральное тестирование. Doctest-основанное тестирование очень тяжело использовать в Zope 3, а в Zope 3 преимущественно используется разработка на тестах (Test-driven Development). Для того, чтобы объяснить идею, давайте представим себе пример использования. Модулю необходима функция, которая возвращает Good morning, name!. name будет предоставлено в качестве аргумента. Перед написанием рабочего кода, напишите для него юнит тест. На самом деле написание кода и тестов осуществляется практически параллельно. Так что создадим файл с именем example1.py и с определением функции:

   1 def goodmorning(name):
   2     "This returns a good morning message"

Заметьте, что вы еще не написали никакой логики. Но очень необходимо запустить тесты успешно с провалами!. Так, теперь создадим файл с именем example1.txt, содержащий примеры использования, используя формат reStructuredText:

Вот тесты для модуля example1.

Сначала импорт модуля:

   1   >>> import example1

Теперь вызов функции goodmorning без аргументов:

   1 >>> example1.goodmorning()
   2 Traceback (most recent call last):
   3 ...
   4 TypeError: goodmorning() takes exactly 1 argument (0 given)

Теперь вызов функции goodmorning с одним аргументом:

   1 >>> example1.goodmorning('Jack')
   2 'Good morning, Jack!'

Заметьте, что примеры написаны так же, как будто выполнены из командной строки. Вы можете использовать командную строку python и вставлять код оттуда. Теперь создадите другой файл test_example1.py со следующим содержимым:

   1 import unittest
   2 import doctest
   3 
   4 def test_suite():
   5     return unittest.TestSuite((
   6         doctest.DocFileSuite('example1.txt'),
   7         ))
   8 
   9 if __name__ == '__main__':
  10     unittest.main(defaultTest='test_suite')

Вот начальный код для запуска теста. Теперь запустим тест командой python2.4 test_example1.py. Вы получите вывод со следующим текстом:

File "example1.txt", line 16, in example1.txt
Failed example:
    example1.goodmorning('Jack')
Expected:
    'Good morning, Jack!'
Got nothing

Как видим, один тест провалился, так что пришло время реализовать функцию:

   1 def goodmorning(name):
   2     "This returns a good morning message"
   3     return "Good morning, %s!" % name

Теперь запустите тест заново, он запустится без провалов.

Теперь подумайте о других функциях, необходимых этому модулю. Перед началом написания кода опишите его в текстовом файле. Опишите API, напишите тест, код, который продолжает этот цикл, пока все требования не будут удовлетворены.

Запуск тестов

Принято класть модули тестирования в модуль tests каждого пакета. Но файлы doctest-ов могут быть отправлены в свой пакет. Например, для пакета ticketcollector. В таком случае основной файл doctest-а может быть размещен по пути ticketcollector/README.txt, и создан пакет zopetic.tests, внутри этого пакета создайте модули тестирования, например test_main.py, test_extra.py и т.д.. Для запуска тестов, перейдите в домашний каталог экземпляра приложения:

   1 $ cd ticketcollector
   2 $ ./bin/test

Компонентная архитектура Zope

Введение

Компонентная архитектура Zope (ZCA) - это фреймворк для поддержки компонент-ориентированного способа разработки приложений. Он очень хорошо подходит для разработки больших программных систем на Python. ZCA - не предназначена специально для BlueBream: она может быть использована в любом Python приложении.

ZCA создавалась для эффективного использования Python объектов. Компоненты - объекты повторного использования с обозримыми интерфейсами. Компонент предоставляет интерфейс, который реализован в классе, или любом другом вызываемом объекте. Неважно как компонент реализован, важно то, чтобы он соответствовал интерфейсу, который он предоставляет. Используя ZCA, вы можете распределить сложность систем на маленькие взаимодействующие компоненты. Она помогает вам в создании двух базовых типов компонентов: адаптеров и утилит.

Существуют два центральных пакета, связанных с ZCA:

  • zope.interface используется для указания интерфейса компонента.

  • zope.component работает с регистрацией и получением компоентов.

Запомните, ZCA, сама по себе не является набором компонентов, а средством для их создания, регистрации, и получения. Также запомните, что адаптер - обычный класс Python (или, в общем случае, фабрика), а утилита - обычный вызываемый Python объект.

ZCA фреймворк разрабатывался как часть проекта BlueBream. Как упоминалось ранее, это чистый Python фреймворк, следовательно может использоваться в любом Python приложении. Существует масса проектов, включая не веб ориентированные, которые его используют.

Адаптеры

Реализация

Этот раздел приводит детальное описание адаптеров. Компонентная архитектура Zope, как вы уже заметили, помогает эффективно использовать Python объекты. Адаптер - один из основных компонентов, которые используются в компонентной архитектуре Zope. Адаптеры - обычные Python объекты, но с хорошо определенным интерфейсом.

Для того, чтобы объявить, что класс является адаптером, используйте функцию adapts, которая определена в пакете zope.component. Вон новый адаптер FrontDeskNG с явным указанием интерфейса:

   1 >>> from zope.interface import implements
   2 >>> from zope.component import adapts
   3 
   4 >>> class FrontDeskNG(object):
   5 ...
   6 ...     implements(IDesk)
   7 ...     adapts(IGuest)
   8 ...
   9 ...     def __init__(self, guest):
  10 ...         self.guest = guest
  11 ...
  12 ...     def register(self):
  13 ...         guest = self.guest
  14 ...         next_id = get_next_id()
  15 ...         bookings_db[next_id] = {
  16 ...         'name': guest.name,
  17 ...         'place': guest.place,
  18 ...         'phone': guest.phone
  19 ...         }

То, что вы определили здесь - адаптер для IDesk, который адаптирует объект IGuest. Интерфейс IDesk реализован в классе FrontDeskNG. Следовательно, экземпляр этого класса предоставляет интерфейс IDesk.

   1 >>> class Guest(object):
   2 ...
   3 ...     implements(IGuest)
   4 ...
   5 ...     def __init__(self, name, place):
   6 ...         self.name = name
   7 ...         self.place = place
   8 
   9 >>> jack = Guest("Jack", "Bangalore")
  10 >>> jack_frontdesk = FrontDeskNG(jack)
  11 
  12 >>> IDesk.providedBy(jack_frontdesk)
  13 True

FrontDeskNG - это просто один адаптер, который вы создали, вы можете создать другие адаптеры, которые выполняют регистрацию гостей по другому.

Регистрация

Для того, чтобы использовать этот адаптер, вам следует зарегистрировать его в реестре компонентов, также известном как менеджер сайта. Менеджер сайта (site manager) обычно присутствует в сайте. Сайт и сайт менеджер более важны при разработке приложения Zope 3. Пока вам следует заботится только о глобальном менеджере сайта (или реестре компонентов). Глобальный менеджер сайта хранится в памяти, а локальный - в долгосрочном хранилище (persistent).

Для того, чтобы зарегистрировать свой собственный компонент, используйте глобальный менеджер сайта:

   1 >>> from zope.component import getGlobalSiteManager
   2 >>> gsm = getGlobalSiteManager()
   3 >>> gsm.registerAdapter(FrontDeskNG,
   4 ...                     (IGuest,), IDesk, 'ng')

Для получения глобального менеджера сайта, вам следует вызвать функцию getGlobalSiteManager из пакета zope.component. На самом деле, глобальный менеджер сайта доступен как атрибут (globalSiteManager) пакета zope.component, так что вы можете использовать его напрямую: zope.component.globalSiteManager. Для регистрации адаптера, как вы увидели выше, используется метод registerAdapter реестра компонентов. Первый аргумент - класс/фабрика адаптера. Второй аргумент - кортеж адаптируемых объектов, то есть, объектов, которые вы адаптируете. В этом примере вы адаптируете только объект IGuest. Третий аргумент - это интерфейс, который реализуется адаптером. Четвертый аргумент не обязательный, и представляет собой имя конкретного адаптера. Если вы даете имя адаптеру, он становится именованным адаптером. Если имя не дано, то оно примет значение пустой строки (‘’).

В предыдущей регистрации вам даны интерфейсы адаптируемого объекта, и предоставляемого адаптером. Так как эта информация уже указана в реализации адаптера, указывать ее еще раз не имеет смысла. Но выполнить регистрацию можно и так:

   1 >>> gsm.registerAdapter(FrontDeskNG, name='ng')

Существует также старый API для выполнения регистраций, которого стоит избегать. Старые функции API начинаются с provide, например: provideAdapter, provideUtility, и т.д. Во время разработки Zope 3 приложения вы можете использовать язык конфигураций ZCML. В Zope 3, локальные компоненты (persistent) могут регистрироваться либо через Zope Management Interface (ZMI), либо программно.

Вы зарегистрировали FrontDeskNG с именем ng. Таким же образом, вы можете зарегистрировать другие адаптеры с разными именами. Если компонент зарегистрирован без имени, то его имя примет значение пустой строки.

Получение адаптера

Получение зарегистрированных компонентов из реестра достигается путем использования двух функций из пакета zope.component. Одна из них - getAdapter, а другая - queryAdapter. Обе функции принимают одинаковые аргументы. getAdapter вызовет исключение ComponentLookupError, если компонент не найден, с другой стороны queryAdapter просто вернет None.

Импорт этих методов выглядит следующим образом:

   1 >>> from zope.component import getAdapter
   2 >>> from zope.component import queryAdapter

В предыдущем разделе вы зарегистрировали объект гость (адаптируемый), который предоставляет интерфейс IDesk с именем ng. В первом разделе этот главы, вы создали объект гость с именем jack.

Вот как можно получить компонент, который адаптирует интерфейс объекта гостя (IGuest) и предоставляет интерфейс IDesk, с именем ng. Вот примеры с getAdapter и queryAdapter:

   1 >>> getAdapter(jack, IDesk, 'ng') #doctest: +ELLIPSIS
   2 <FrontDeskNG object at ...>
   3 >>> queryAdapter(jack, IDesk, 'ng') #doctest: +ELLIPSIS
   4 <FrontDeskNG object at ...>

Как видите, первым аргументом идет адаптируемый объект, потом интерфейс, который должен предоставлять адаптер, и последний - имя адаптера.

Если вы попытаетесь искать компонент с именем, которое не было использовано при регистрации, но для того же адаптируемого объекта и интерфейса, то адаптер будет не найден. Вот как эти два метода ведут себя в таком случае:

   1 >>> getAdapter(jack, IDesk, 'not-exists') #doctest: +ELLIPSIS
   2 Traceback (most recent call last):
   3 ...
   4 ComponentLookupError: ...
   5 >>> reg = queryAdapter(jack,
   6 ...           IDesk, 'not-exists') #doctest: +ELLIPSIS
   7 >>> reg is None
   8 True

Как видите в примере, getAdapter вызвал исключение ComponentLookupError, а queryAdapter просто вернул None, когда поиск закончился неудачей.

Третий аргумент, регистрационное имя, - не обязательный. Если третий аргумент не предоставлен, он станет пустой строкой (‘’). Так как нет компонента под именем "пустая строка", getAdapter вызовет исключение ComponentLookupError. Таким же образом queryAdapter вернет None, посмотрите сами как это работает:

   1 >>> getAdapter(jack, IDesk) #doctest: +ELLIPSIS
   2 Traceback (most recent call last):
   3 ...
   4 ComponentLookupError: ...
   5 >>> reg = queryAdapter(jack, IDesk) #doctest: +ELLIPSIS
   6 >>> reg is None
   7 True

В этом разделе вы узнали, как регистрировать простой адаптер и как получить его из реестра компонентов. Эти адаптеры, являются так называемыми одиночными адаптерами, потому что адаптируют только один объект. Если адаптер адаптирует более одного объекта, то он называется мультиадаптером.

Получения адаптера по интерфейсу

Адаптеры могут быть получены напрямую при использовании интерфейсов, но этот метод работает только для не именованных адаптеров. Первый аргумент - адаптируемый объект, второй - не обязательный аргумент. Если поиск адаптера заканчивается неудачей, результатом выполнения станет второй аргумент.

   1 >>> IDesk(jack, alternate='default-output')
   2 'default-output'

Ключевое имя может быть опущено:

   1 >>> IDesk(jack, 'default-output')
   2 'default-output'

Если второй аргумент не предоставлен, то будет вызвано исключение TypeError:

   1 >>> IDesk(jack) #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
   2 Traceback (most recent call last):
   3 ...
   4 TypeError: ('Could not adapt',
   5   <Guest object at ...>,
   6   <InterfaceClass __builtin__.IDesk>)

Вот регистрация FrontDeskNG без имени:

   1 >>> gsm.registerAdapter(FrontDeskNG)

Теперь поиск адаптера должен увенчаться успехом:

   1 >>> IDesk(jack, 'default-output') #doctest: +ELLIPSIS
   2 <FrontDeskNG object at ...>

В простых случаях вы можете свободно использовать интерфейс для получения адаптера.

Утилита

Теперь вам должны быть понятны концепции интерфейса, адаптера и реестра компонентов. Иногда очень полезно иметь возможность регистрировать объект, который не адаптирует ничего. Соединение к базе данных, XML анализатор, объект, который возвращает уникальные идентификаторы, и т.д., - примеры таких объектов. Такие компоненты предоставляются ZCA и называются утилитами.

Утилиты - простые объекты, которые предоставляют интерфейс, и ищутся по интерфейсу и имени. Такой подход создает глобальный реестр с экземплярами, которые могут быть зарегистрированы и получены с любого места вашего приложения, и не нужно передавать их экземпляры как параметры выполняемых методов.

Вам не следует регистрировать все компоненты таким образом. Регистрируйте только те, которые вы хотите в будущем заменять.

Простая утилита

Утилита может регистрироваться с именем, или без имени. Утилита, которая регистрируется с именем называется именованной, и это вы увидите в следующем разделе. Перед реализацией утилиты, как обычно, следует определить ее интерфейс. Вот пример:

   1 >>> from zope.interface import Interface
   2 >>> from zope.interface import implements
   3 
   4 >>> class IGreeter(Interface):
   5 ...
   6 ...     def greet(name):
   7 ...         """Say hello"""

Как и адаптер, утилита может иметь более одной реализации. Вот возможная реализация предыдущего интерфейса:

   1 >>> class Greeter(object):
   2 ...
   3 ...     implements(IGreeter)
   4 ...
   5 ...     def greet(self, name):
   6 ...         return "Hello " + name

Реальная утилита будет экземпляром приведенного класса. Чтобы использовать эту утилиту, ее нужно зарегистрировать, а потом получить с помощью ZCA API. Вы можете зарегистрировать экземпляр этого класса (утилиту) с помощью registerUtility:

   1 >>> from zope.component import getGlobalSiteManager
   2 >>> gsm = getGlobalSiteManager()
   3 
   4 >>> greet = Greeter()
   5 >>> gsm.registerUtility(greet, IGreeter)

В этом примере вы зарегистрировали утилиту, которая предоставляет интерфейс IGreeter. Вы можете выполнить поиск утилиты с помощью queryUtility или getUtility:

   1 >>> from zope.component import queryUtility
   2 >>> from zope.component import getUtility
   3 
   4 >>> queryUtility(IGreeter).greet('Jack')
   5 'Hello Jack'
   6 
   7 >>> getUtility(IGreeter).greet('Jack')
   8 'Hello Jack'

Как видите, адаптеры - обычные классы, а утилиты - экземпляры классов. Вы создаете экземпляр класса утилиты один раз, а экземпляры адаптеров создаются динамически при их вызове.

Именованная утилита

При регистрации утилиты, как и адаптера, вы можете использовать имя. Как упоминалось в предыдущем разделе, утилита, которая регистрируется с определенным именем, называется именованной утилитой.

Вот как можно зарегистрировать утилиту с именем:

   1 >>> greet = Greeter()
   2 >>> gsm.registerUtility(greet, IGreeter, 'new')

В этом примере вы зарегистрировали утилиту с именем, которая предоставляет интерфейс IGreeter. Вы можете выполнить поиск по интерфейсу с помощью queryUtility или getUtility:

   1 >>> from zope.component import queryUtility
   2 >>> from zope.component import getUtility
   3 
   4 >>> queryUtility(IGreeter, 'new').greet('Jill')
   5 'Hello Jill'
   6 
   7 >>> getUtility(IGreeter, 'new').greet('Jill')
   8 'Hello Jill'

Как видите при выполнении поискового запроса вам следует указывать имя вторым аргументом.

Вызов функции getUtility без имени (второй аргумент) равнозначно вызову с пустой строкой в качестве имени, так как имя по умолчанию для второго (ключевого) аргумента - пустая строка. Тогда механизм поиска попытается найти компонент без имени (), и потерпит неудачу. Когда поиск компонента неудачен, вызывается исключение ComponentLookupError. Запомните, что ни в коем случае не будет возвращен случайный объект с тем или иным именем. Функции поиска адаптеров getAdapter и queryAdapter работают таким же образом.

Конфигурация

Введение

Developing components alone does not make a framework. There must be some configuration utility that tells the system how the components work together to create the application server framework. This is done using the Zope Configuration Markup Language (ZCML) for all filesystem-based code. Therefore it is very important that a developer knows how to use ZCML to hook up his/her components to the framework.

As stated above, it became necessary to develop a method to setup and configure the components that make up the application server. While it might seem otherwise, it is not that easy to develop an effective configuration system, since there are several requirements that must be satisfied. Over time the following high-level requirements developed that caused revisions of the implementation and coding styles to be created:

While the developer is certainly the one that writes the initial cut of the configuration, this user is not the real target audience. Once the product is written, you would expect a system administrator to interact much more frequently with the configuration, adding and removing functionality or adjust the configuration of the server setup. System administrators are often not developers, so that it would be unfortunate to write the configuration in the programming language, here Python. But an administrator is familiar with configuration scripts, shell code and XML to some extend. Therefore an easy to read syntax that is similar to other configuration files is of advantage. Since the configuration is not written in Python, it is very important that the tight integration with Python is given. For example, it must be very simple to refer to the components in the Python modules and to internationalize any human-readable strings. The configuration mechanism should be declarative and not provide any facilities for logical operations. If the configuration would support logic, it would become very hard to read and the initial state of the entire system would be unclear. This is another reason Python was not suited for this task. Developing new components sometimes requires to extend the configuration mechanism. So it must be easy for the developer to extend the configuration mechanism without much hassle. To satisfy the first requirement, we decided to use an XML-based language (as the name already suggests). The advantage of XML is also that it is a “standard format”, which increases the likelihood for people to be able to read it right away. Furthermore, we can use standard Python modules to parse the format and XML namespaces help us to group the configuration by functionality.

A single configuration step is called a directive. Each directive is an XML tag, and therefore the tags are grouped by namespaces. Directives are done either by simple or complex directives. Complex directives can contain other sub-directives. They are usually used to provide a set of common properties, but do not generate an action immediately.

A typical configuration file would be:

<configure

  • <adapter

    • factory="product.FromIXToIY" for="product.interfaces.IX"

      provides="product.interfaces.IY" />

</configure> All configuration files are wrapped by the configure tag, which represents the beginning of the configuration. In the opening of this tag, we always list the namespaces we wish to use in this configuration file. Here we only want to use the generic Zope 3 namespace, which is used as the default. Then we register an adapter with the system on line 4-7. The interfaces and classes are referred to by a proper Python dotted name. The configure tag might also contain an i18n_domain attribute that contains the domain that is used for all the translatable strings in the configuration.

As everywhere in Zope 3, there are several naming and coding conventions for ZCML inside a package. By default you should name the configuration file configure.zcml. Inside the file you should only declare namespaces that you are actually going to use. When writing the directives make sure to logically group directives together and use comments as necessary. Comments are written using the common XML syntax: <!–...–>. For more info see Steve’s detailed ZCML Style Guide at http://dev.zope.org/Zope3/ZCMLStyleGuide for more info.

To satisfy our fourth requirement, it is possible to easily extend ZCML through itself using the meta namespace. A directive can be completely described by four components, its name, the namespace it belongs to, the schema and the directive handler:

<meta:directive

These meta-directives are commonly placed in a file called meta.zcml.

The schema of a directive, which commonly lives in a file called metadirectives.py, is a simple Zope 3 schema whose fields describe the available attributes for the directive. The configuration system uses the fields to convert and validate the values of the configuration for use. For example, dotted names are automatically converted to Python objects. There are several specialized fields specifically for the configuration machinery:

PythonIndentifier - This field describes a python identifier, for example a simple variable name. GlobalObject - An object that can be accessed as a module global, such as a class, function or constant. Tokens - A sequence that can be read from a space-separated string. The value_type of the field describes token type. Path - A file path name, which may be input as a relative path. Input paths are converted to absolute paths and normalized. Bool - An extended boolean value. Values may be input (in upper or lower case) as any of: yes, no, y, n, true, false, t, or f. MessageID - Text string that should be translated. Therefore the directive schema is the only place that needs to deal with internationalization. This satisfies part of requirement 2 above. The handler, which commonly lives in a file called metaconfigure.py, is a function or another callable object that knows what needs to be done with the given information of the directive. Here is a simple (simplified to the actual code) example:

def adapter(_context, factory, provides, for_, name=):

  • _context.action(
    • discriminator = ('adapter', for_, provides, name), callable = provideAdapter, args = (for_, provides, factory, name), )

The first argument of the handler is always the _context variable, which has a similar function to self in classes. It provides some common methods necessary for handling directives. The following arguments are the attributes of the directive (and their names must match). If an attribute name equals a Python keyword, like for in the example, then an underscore is appended to the attribute name.

The handler should also not directly execute an action, since the system should first go through all the configuration and detect possible conflicts and overrides. Therefore the _context object has a method called action that registers an action to be executed at the end of the configuration process. The first argument is the discriminator, which uniquely defines a specific directive. The callable is the function that is executed to provoke the action, the args argument is a list of arguments that is passed to the callable and the kw contains the callable’s keywords.

As you can see, there is nothing inheritly difficult about ZCML. Still, people coming to Zope 3 often experience ZCML as the most difficult part to understand. This often created huge discussions about the format of ZCML. However, I believe that the problem lies not within ZCML itself, but the task it tries to accomplish. The components themselves always seem so clean in implementation; and then you get to the configuration. There you have to register this adapter and that view, make security assertions, and so on. And this in itself seems overwhelming at first sight. When I look at a configuration file after a long time I often have this feeling too, but reading directive for directive often helps me to get a quick overview of the functionality of the package. In fact, the configuration files can help you understand the processes of the Zope 3 framework without reading the code, since all of the interesting interactions are defined right there.

Furthermore, ZCML is well documented at many places, including the Zope 3 API documentation tool at http://localhost:8080/++apidoc++/. Here is a short list of the most important namespaces:

zope - This is the most generic and fundamental namespace of all, since it allows you to register all basic components with the component architecture. browser - This namespace contains all of the directives that deal with HTML output, including managing skins and layer, declare new views (pages) and resources as well as setup auto-generated forms. meta - As discussed above, you can use this namespace to extend available directives. xmlrpc - This is the equivalent to browser, except that allows one to specify methods of components that should be available via XML-RPC. i18n - This namespace contains all internationalization- and localization-specific configuration. Using registerTranslations you can register new message catalogs with a translation domain. help - Using the register directive, you can register new help pages with the help system. This will give you context-sensitive help for the ZMI screens of your products. mail - Using the directives of this namespace you can setup mailing components that your application can use to

Запуск

Введение

The BlueBream framework creates WSGI applications, which can run behind any WSGI compliant web servers. The main application is configured and created via a factory function defined inside startup.py. This functions returns the WSGI complaint application object to the server. For example, in the “ticket collector” tutorial, you can see the factory function defined in src/tc/main/startup.py file:

import zope.app.wsgi

def application_factory(global_conf):

  • zope_conf = global_conf['zope_conf'] return zope.app.wsgi.getWSGIApplication(zope_conf)

PaseDeploy together with PasteScript are then used to run the WSGI application. However, any WSGI server can be used to run BlueBream application [1]. We provide PaseDeploy with the WSGI application factory as an entry point. For example, in the “ticket collector” tutorial, you can see the entry point defined in setup.py file:

[paste.app_factory] main = tc.main.startup:application_factory The application can now be launched as a web service using the paster serve command provided by PasteScript. To configure the web server, an INI file has to be passed to the command as an argument. The INI file defines the WSGI application, any WSGI middleware we may want and web server options. For example, in the “ticket collector” tutorial, you can see the WSGI application defined in deploy.ini file:

[app:main] use = egg:ticketcollector

[server:main] use = egg:Paste#http host = 127.0.0.1 port = 8080

[DEFAULT] # set the name of the zope.conf file zope_conf = %(here)s/etc/zope.conf You can read more about PasteDeploy and PasteScript in the PythonPaste site.

Запуск WSGI приложения

When you run BlueBream application using the paster server command, you can see something like this:

$ ./bin/paster serve deploy.ini ... Starting server in PID 13367. serving on http://127.0.0.1:8080 [1] WSGI servers like mod_wsgi don’t require the paster serve command provided by PasteDeploy to run the WSGI server.

Функциональное тестирование

Введение

In this chapter, you will learn more about functional testing. A doctest based package (zope.testbrowser) is used in BlueBream for functional testing. Unlike unit tests, functional tests are user interface (view) oriented.

zope.testbrowser

The central part of this package is a browser object. This create this object, import Browser class from zope.testbrowser.testing:

>>> from zope.testbrowser.testing import Browser >>> browser = Browser() To learn the usage of package, first create a simple HTML page with following content:

<html>

  • <head>

    • <title>Test Page</title>

    </head> <body>

    • <h1>Test Page</h1>

    </body>

</html> To open a page:

>>> browser.open('http://localhost/zopetest/simple.html') >>> browser.url 'http://localhost/zopetest/simple.html'

Слой тестирования

Запуск тестов

Your test suites can be placed in tests.py module under each packages. By default, in BlueBream there will be a tests.py with one test suite created using z3c.testsetup:

import z3c.testsetup

test_suite = z3c.testsetup.register_all_tests('tc.main') The z3c.testsetup will aut-recover test suites from doctest files. You can create your doctest files, similar to example given in README.txt:

ticketcollector

:doctest: :functional-zcml-layer: ftesting.zcml

Open browser and test::

  • >>> from zope.testbrowser.testing import Browser >>> browser = Browser() >>> browser.open('http://localhost/@@index') >>> 'Welcome to BlueBream' in browser.contents True

The fouth line specifies that a ZCML file named ftesting.zcml is required to setup the test layer.

To run the tests:

$ ./bin/test

Скины

Введение

It is often required to build web applications with equal/similar features, but different look and feel. Variation of look and feel can be simple or complex. It can be either just an exchange of a CSS file and some images. Sometimes, you will be required to reconfigure the application to have different user interface components, such as widgets, tables etc. Also you will be required to mix and match user interface components from multiple packages.

In BlueBream, the skin concept is implemented to use the Zope component architecture.

There are two terms associated with skinning named, layer and skin. Before proceeding, it would be better to understand the meaning of these two terms in BlueBream skinning. Skins are directly provided by a request

Слои

Define the “feel” of a site Contain presentation logic Common artifacts: pages, content providers, viewlet managers, and viewlets Developed by BlueBream (Python) developers

Скины

Define the “look” of a site Common artifacts: templates and resources (CSS, Javascript, etc.) Use layers to retrieve the data for templates Developed by HTML and Graphic Designer/Scripter

Слои против скинов

Both are implemented as interfaces BlueBream does not differentiate between the two In fact, the distinction of layers defining the “feel” and skins the “look” is a convention. You may not want to follow the convention, if it is too abstract for you, but if you are developing application with multiple look and feel, I strongly suggest using this convention, since it cleanly separates concerns. Both support inheritance/acquisition This is realized through a combination of interface inheritance and component lookup techniques. This book will discuss this in more detail later.

Unfortunately, it is hard to reuse the UI components developed for these skins, since they still rely on the not so flexible macro pattern. Thus, it is better if you start from scratch. This will also avoid a lot of the overhead that comes with the over-generalized core skins.

Новый скин

Views registered for default layer by default zope.publisher.interfaces.browser.IDefaultBrowserLayer Default layer contains a lot of things you do not need (security concerns) Since pretty much everything in zope.app is registered into the default layer, it has an uncontrollable amount of junk in it. It is very hard to verify that all those registrations fulfill your security needs. Another problem is that views might be available that you do not want to be available. Always want to develop skins from scratch Some registrations in the default layer are very useful Examples of those useful registrations include error views, traversal registrations, and widgets.

Установка слоя

Write an interface for the layer that inherits the minimal layer:

from zope.publisher.interfaces.browser import IBrowserSkinType

class IHelloWorldLayer(IBrowserSkinType):

  • """Hello World Application Layer"""

Change all page, viewletmanager, and viewlet directives to specify this layer:

layer=".interfaces.IHelloWorldLayer" Once you changed those registrations, the helloworld.html page will not be available anymore in the core skins. The templates by themselves do not matter.

Использование слоя

Registering views and resources is not any different now, but you can simply register them on the skin directly:

<browser:resource

  • name="zope3logo.gif" file="images/zope3logo.gif" layer=".interfaces.IBasicSkin"

    />

As you can see, you don’t have to create an extra layer just to create a custom skin. You were also able to use standard Component Architecture ZCML directives instead of custom ones whose special syntax the developer needs to remember additionally.

A typical browser:page with with layer specified is like this:

<browser:page

  • for="*" name="dialog_macros" permission="zope.View" layer=".interfaces.IBasicSkin" template="dialog_macros.pt"

    />

Установка скина

Skins are technically interfaces defined using zope.interface package. Write an interface for each new skin that inherits the Hello World application layer:

class IBasicSkin(IHelloWorldLayer):

  • """Basic Skin for Hello World App."""

To register this you can use interface and utility directives in zope namespace. The type of the IShanghaiSkin skin is zope.publisher.interfaces.browser.IBrowserSkinType. Here is a sample configure.zcml:

<interface

  • interface=".interfaces.IBasicSkin" type="zope.publisher.interfaces.browser.IBrowserSkinType"

    />

<utility

  • component=".interfaces.IBasicSkin" provides="zope.publisher.interfaces.browser.IBrowserSkinType"

    name="BasicSkin" />

As a shortcut, you can also just use the interface directive and pass the new name parameter. The following one directive has the same effect as the two above regarding the skin registration:

<interface

  • interface=".interfaces.IBasicSkin" type="zope.publisher.interfaces.browser.IBrowserSkinType"

    name="BasicSkin" />

Register all templates for this skin by adding the layer attribute:

layer=".interfaces.IBasicSkin"

Использование скина

Access it via: http://localhost:8080/++skin++BasicSkin

Hide skin traversal step by using Apache’s Virtual Hosting feature

To change the default skin to something else use:

<browser:defaultSkin name="BasicSkin" /> Simply specifying the browser:defaultSkin directive in your configuration file will not work, since it has been specified in zope/app/zcmlfiles/browser.zcml already. You can either change the skin at this location or use the zope:includeOverrides directive, which will override the any included directives.

Итоги

This chapter introduced skinnig in BlueBream.

Перевод: Ростислав Дзинько

Документации/Bluebream/Руководство2 (последним исправлял пользователь RostislavDzinko 2010-07-12 22:56:48)