Различия между версиями 1 и 18 (по 17 версиям)
Версия 1 от 2010-07-13 09:07:17
Размер: 502
Редактор: RostislavDzinko
Комментарий:
Версия 18 от 2010-07-13 13:21:34
Размер: 43439
Редактор: RostislavDzinko
Комментарий:
Удаления помечены так. Добавления помечены так.
Строка 8: Строка 8:

= Важные требования =

 * разделение реализации в обновлении частей отрисовки. Такой подход позволяет манипулировать данными после обновления, и перед отрисовкой.
 * мы можем использовать шаблоны страниц, если потребуется, но, по умолчанию, все делается в Python.
 * мы может отрисовывать порционную навигацию отдельно от самой таблицы.

= Никаких скинов =

Этот пакет не предоставляет никаких шаблонов и скинов. В любом случае, когда вам нужно отрисовать красивую таблицу, вам придется писать свой собственный скин или шаблон. Отсутствие шаблонов и скинов позволяет удостоверится, что ''z3c.table'' имеет очень мало зависимостей, а поэтому легко поддается повторному использованию.

= Заметка =

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

= Пример установки данных =

Таблицы часто используются для отображения нормализованных порций данных. Например, нам необходимо отобразить информацию о файлах в папке. Каждый файл имеет заголовок, размер и тип. Наша таблица должна иметь строку для каждого файла и столбец для этих трех полей данных (заголовка, размера, и типа). Контекстом таблицы всегда является структура данных, по которой можно выполнить итерацию, при этом каждый элемент последовательности соответствует строке таблицы. Давайте создадим папку, которую мы сможем использовать как контекст:

{{{#!highlight python
>>> from zope.app.container import btree
>>> class Folder(btree.BTreeContainer):
... """Sample folder."""
... __name__ = u'folder'
>>> folder = Folder()
}}}

XXX: не уверен, куда нам нужно положить эту папку. Также нам можно и не давать значение атрибуту '''_ _name_ _'''. Нам не нужно куда либо помещать ее. Давайте установим родительский элемент для папки:

{{{#!highlight python
>>> root['folder'] = folder
}}}

Теперь создадим простой объект '''File''' для наполнения созданной нами папки.

{{{#!highlight python
>>> class File(object):
... """Sample file."""
... def __init__(self, title, size, type=None):
... self.title = title
... self.number = size
... self.type = type
}}}

Теперь давайте наполним созданную папку файлами.

{{{#!highlight python
>>> folder[u'first'] = File('First', 1)
>>> folder[u'second'] = File('Second', 2)
>>> folder[u'third'] = File('Third', 3)
}}}

= Создание таблиц =

Теперь, когда у нас есть тестовые данные, с которыми можно работать, мы может создать таблицу. Так как таблицы - компоненты пользовательского интерфейса, они требуют и контекста и запроса (request). Они передаются как аргументы конструктору класса '''Table'''.

{{{#!highlight python
>>> from zope.publisher.browser import TestRequest
>>> from z3c.table import table
>>> request = TestRequest()
>>> plainTable = table.Table(folder, request)
}}}

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

{{{#!highlight python
>>> plainTable.update()
>>> plainTable.render()
u''
}}}

Также стоит заметить, что класс '''Table''' - реализация интерфейса '''ITable'''. Намного интереснее взглянуть, что предоставляет '''ITable''', когда у нас есть несколько столбцов:

{{{#!highlight python
>>> from z3c.table import interfaces
>>> from zope.interface.verify import verifyObject
>>> verifyObject(interfaces.ITable, plainTable)
True
}}}

= Создание столбцов =

Так как нам может понадобится некий тип столбцов в многих разных таблицах, определение столбца лежит отдельно от самой таблицы. Каждый тип столбцов представлен собственным классом, который реализует интерфейс '''IColumn'''. Чтобы помочь в определении столбца, существует базовый класс '''Column'''. Простая колонка, которая отображает заголовок элемента выглядит примерно так:

{{{#!highlight python
>>> from z3c.table import column
>>> class TitleColumn(column.Column):
...
... weight = 10
... header = u'Title'
...
... def renderCell(self, item):
... return u'Title: %s' % item.title
}}}

Атрибут '''header''' - это текст, который мы можем увидеть в заголовке таблицы, обычно это тег '''<th>'''. Атрибут '''weight''' указывает порядок колонки в таблице относительно других колонок. Метод '''renderCell''' делает всю работу по отображению, возвращая html структуру, которая будет находится внутри колонки таблица, обычно это тег '''<td>'''. Метод '''renderCell''' должен предоставляться потомками класса '''Column'''.

= Добавление колонки в таблицу с использованием адаптеров =

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

{{{#!highlight python
>>> import zope.component
>>> zope.component.provideAdapter(TitleColumn,
... (None, None, interfaces.ITable), provides=interfaces.IColumn,
... name='firstColumn')
}}}

Теперь отрисуем таблицу еще раз:

{{{#!highlight python
>>> plainTable.update()
>>> print plainTable.render()
}}}
{{{#!highlight html
<table>
  <thead>
    <tr>
      <th>Title</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Title: First</td>
    </tr>
    <tr>
      <td>Title: Second</td>
    </tr>
    <tr>
      <td>Title: Third</td>
    </tr>
  </tbody>
</table>
}}}

Мы также можем использовать предопределенное имя столбца:

{{{#!highlight python
>>> zope.component.provideAdapter(column.NameColumn,
... (None, None, interfaces.ITable), provides=interfaces.IColumn,
... name='secondColumn')
}}}

Теперь мы получим еще один дополнительный столбец:

{{{#!highlight python
>>> plainTable.update()
>>> print plainTable.render()
}}}
{{{#!highlight html
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Title</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>first</td>
      <td>Title: First</td>
    </tr>
    <tr>
      <td>second</td>
      <td>Title: Second</td>
    </tr>
    <tr>
      <td>third</td>
      <td>Title: Third</td>
    </tr>
  </tbody>
</table>
}}}

= Объединение ячеек =

Теперь давайте посмотрим, как можно сделать объединение ячеек для столбца:

{{{#!highlight python
>>> class ColspanColumn(column.NameColumn):
...
... weight = 999
...
... def getColspan(self, item):
... # colspan condition
... if item.__name__ == 'first':
... return 2
... else:
... return 0
...
... def renderHeadCell(self):
... return u'Colspan'
...
... def renderCell(self, item):
... return u'colspan: %s' % item.title
}}}

Теперь зарегистрируем адаптер этого столбца как '''colspanColumn''':

{{{#!highlight python
>>> zope.component.provideAdapter(ColspanColumn,
... (None, None, interfaces.ITable), provides=interfaces.IColumn,
... name='colspanColumn')
}}}

Теперь вы видите, насколько объединение через '''!ColspanAdapter''' больше, чем столбцы таблицы. Такой код вызовет исключение '''!ValueError''':

{{{#!highlight python
>>> plainTable.update()
...
ValueError: Colspan for column '<ColspanColumn u'colspanColumn'>' larger then table.
}}}

Но если мы установим столбец первой строчкой, таблица отрисуется корректно:

{{{#!highlight python
>>> class CorrectColspanColumn(ColspanColumn):
... """Colspan with correct weight."""
...
... weight = 0
}}}

Зарегистрируйте и отрисуйте таблицу еще раз:

{{{#!highlight python
>>> zope.component.provideAdapter(CorrectColspanColumn,
... (None, None, interfaces.ITable), provides=interfaces.IColumn,
... name='colspanColumn')
>>> plainTable.update()
>>> print plainTable.render()
}}}

{{{#!highlight html
<table>
  <thead>
    <tr>
      <th>Colspan</th>
      <th>Name</th>
      <th>Title</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td colspan="2">colspan: First</td>
      <td>Title: First</td>
    </tr>
    <tr>
      <td>colspan: Second</td>
      <td>second</td>
      <td>Title: Second</td>
    </tr>
    <tr>
      <td>colspan: Third</td>
      <td>third</td>
      <td>Title: Third</td>
    </tr>
  </tbody>
</table>
}}}

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

Существующая реализация позволяет определять таблицу в классе без использования адаптеров.

Сначала нужно определить столбец, который сможет отобразить значения наших элементов:

{{{#!highlight python
>>> class SimpleColumn(column.Column):
...
... weight = 0
...
... def renderCell(self, item):
... return item.title
}}}

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

{{{#!highlight python
>>> class PrivateTable(table.Table):
...
... def setUpColumns(self):
... firstColumn = TitleColumn(self.context, self.request, self)
... firstColumn.__name__ = u'title'
... firstColumn.weight = 1
... secondColumn = SimpleColumn(self.context, self.request, self)
... secondColumn.__name__ = u'simple'
... secondColumn.weight = 2
... secondColumn.header = u'The second column'
... return [secondColumn, firstColumn]
}}}

Теперь мы можете создавать, обновлять и отображать таблицу:

{{{#!highlight python
>>> privateTable = PrivateTable(folder, request)
>>> privateTable.update()
>>> print privateTable.render()
}}}
{{{#!highlight html
<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>The second column</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Title: First</td>
      <td>First</td>
    </tr>
    <tr>
      <td>Title: Second</td>
      <td>Second</td>
    </tr>
    <tr>
      <td>Title: Third</td>
      <td>Third</td>
    </tr>
  </tbody>
</table>
}}}

= Каскадные таблицы стилей =

Наша реализация таблицы и столбца поддерживает установку классов css. Давайте определим таблицу и столбцы с некими значениями css:

{{{#!highlight python
>>> class CSSTable(table.Table):
...
... cssClasses = {'table': 'table',
... 'thead': 'thead',
... 'tbody': 'tbody',
... 'th': 'th',
... 'tr': 'tr',
... 'td': 'td'}
...
... def setUpColumns(self):
... firstColumn = TitleColumn(self.context, self.request, self)
... firstColumn.__name__ = u'title'
... firstColumn.__parent__ = self
... firstColumn.weight = 1
... firstColumn.cssClasses = {'th':'thCol', 'td':'tdCol'}
... secondColumn = SimpleColumn(self.context, self.request, self)
... secondColumn.__name__ = u'simple'
... secondColumn.__parent__ = self
... secondColumn.weight = 2
... secondColumn.header = u'The second column'
... return [secondColumn, firstColumn]
}}}

Теперь посмотрит, как отображается такая таблица с учетом присвоенных значений css классов. Заметьте, что '''th''' и '''td''' получили из таблицы и из колонки.

{{{#!highlight python
>>> cssTable = CSSTable(folder, request)
>>> cssTable.update()
>>> print cssTable.render()
}}}
{{{#!highlight html
<table class="table">
  <thead class="thead">
    <tr class="tr">
      <th class="thCol th">Title</th>
      <th class="th">The second column</th>
    </tr>
  </thead>
  <tbody class="tbody">
    <tr class="tr">
      <td class="tdCol td">Title: First</td>
      <td class="td">First</td>
    </tr>
    <tr class="tr">
      <td class="tdCol td">Title: Second</td>
      <td class="td">Second</td>
    </tr>
    <tr class="tr">
      <td class="tdCol td">Title: Third</td>
      <td class="td">Third</td>
    </tr>
  </tbody>
</table>
}}}

= Перемежающиеся таблицы =

Мы поддерживаем встроенную поддержку перемежающихся строк таблицы, которые основаны на парных и непарных классах CSS. Давайте определим таблицу, включая другие CSS классы. Для поддержки парности/непарности нам следует определить классу '''cssClassEven''' и '''cssClassOdd''' CSS:

{{{#!highlight python
>>> class AlternatingTable(table.Table):
...
... cssClasses = {'table': 'table',
... 'thead': 'thead',
... 'tbody': 'tbody',
... 'th': 'th',
... 'tr': 'tr',
... 'td': 'td'}
...
... cssClassEven = u'even'
... cssClassOdd = u'odd'
...
... def setUpColumns(self):
... firstColumn = TitleColumn(self.context, self.request, self)
... firstColumn.__name__ = u'title'
... firstColumn.__parent__ = self
... firstColumn.weight = 1
... firstColumn.cssClasses = {'th':'thCol', 'td':'tdCol'}
... secondColumn = SimpleColumn(self.context, self.request, self)
... secondColumn.__name__ = u'simple'
... secondColumn.__parent__ = self
... secondColumn.weight = 2
... secondColumn.header = u'The second column'
... return [secondColumn, firstColumn]
}}}

Теперь обновите и отобразите новую таблицу. Как видите, к данному '''tr''' классу добавились дополнительные классы '''even''' и '''odd''':

{{{#!highlight python
>>> alternatingTable = AlternatingTable(folder, request)
>>> alternatingTable.update()
>>> print alternatingTable.render()
}}}
{{{#!highlight html
<table class="table">
  <thead class="thead">
    <tr class="tr">
      <th class="thCol th">Title</th>
      <th class="th">The second column</th>
    </tr>
  </thead>
  <tbody class="tbody">
    <tr class="even tr">
      <td class="tdCol td">Title: First</td>
      <td class="td">First</td>
    </tr>
    <tr class="odd tr">
      <td class="tdCol td">Title: Second</td>
      <td class="td">Second</td>
    </tr>
    <tr class="even tr">
      <td class="tdCol td">Title: Third</td>
      <td class="td">Third</td>
    </tr>
  </tbody>
</table>
}}}

= Сортировка таблицы =

Еще одно свойство таблицы - поддержка сортировки данных по столбцам. Так как сортировка данных таблицы вещь очень важная, мы предлагаем ее по умолчанию. Но ее можно использовать только тогда, когда установлено значение '''sortOn'''. Вы можете установить это значение на уровне класса, добавляя значение '''defaultSortOn''' или устанавливая его как значение в запросе. Мы покажем вам, как это сделать позже. Нам также потребуются столбцы, которые помогут продемонстрировать хороший пример сортировки. Наш новый столбец, по которому будем сортировать, будет использовать атрибут '''number''' элементов содержимого в качестве критерия сортировки:

{{{#!highlight python
>>> class NumberColumn(column.Column):
...
... header = u'Number'
... weight = 20
...
... def getSortKey(self, item):
... return item.number
...
... def renderCell(self, item):
... return 'number: %s' % item.number
}}}

Теперь давайте установим таблицу:

{{{#!highlight python
>>> class SortingTable(table.Table):
...
... def setUpColumns(self):
... firstColumn = TitleColumn(self.context, self.request, self)
... firstColumn.__name__ = u'title'
... firstColumn.__parent__ = self
... secondColumn = NumberColumn(self.context, self.request, self)
... secondColumn.__name__ = u'number'
... secondColumn.__parent__ = self
... return [firstColumn, secondColumn]
}}}

Нам также потребуется больше элементов в контейнере, которые мы будем сортировать:

{{{#!highlight python
>>> folder[u'fourth'] = File('Fourth', 4)
>>> folder[u'zero'] = File('Zero', 0)
}}}

Давайте отобразим из без установленного значения '''sortOn''':

{{{#!highlight python
>>> sortingTable = SortingTable(folder, request)
>>> sortingTable.update()
>>> print sortingTable.render()
}}}
{{{#!highlight html
<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Number</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Title: First</td>
      <td>number: 1</td>
    </tr>
    <tr>
      <td>Title: Fourth</td>
      <td>number: 4</td>
    </tr>
    <tr>
      <td>Title: Second</td>
      <td>number: 2</td>
    </tr>
    <tr>
      <td>Title: Third</td>
      <td>number: 3</td>
    </tr>
    <tr>
      <td>Title: Zero</td>
      <td>number: 0</td>
    </tr>
  </tbody>
</table>
}}}

Как видите, эта таблица не предоставляет никакого явного порядка. Давайте определим индекс столбца, по которому будем сортировать данные:

{{{#!highlight python
>>> sortOnId = sortingTable.rows[0][1][1].id
>>> sortOnId
u'table-number-1'
}}}

Теперь давайте используем найденный индекс как значение '''sortOn''':

{{{#!highlight python
>>> sortingTable.sortOn = sortOnId
}}}

Важным моментом является обновление таблицы после установки сортировки по значению:

{{{#!highlight python
>>> sortingTable.update()
>>> print sortingTable.render()
}}}
{{{#!highlight html
<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Number</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Title: Zero</td>
      <td>number: 0</td>
    </tr>
    <tr>
      <td>Title: First</td>
      <td>number: 1</td>
    </tr>
    <tr>
      <td>Title: Second</td>
      <td>number: 2</td>
    </tr>
    <tr>
      <td>Title: Third</td>
      <td>number: 3</td>
    </tr>
    <tr>
      <td>Title: Fourth</td>
      <td>number: 4</td>
    </tr>
  </tbody>
</table>
}}}

Мы также можем инвертировать порядок сортировки:

{{{#!highlight python
>>> sortingTable.sortOrder = 'reverse'
>>> sortingTable.update()
>>> print sortingTable.render()
}}}
{{{#!highlight html
<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Number</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Title: Fourth</td>
      <td>number: 4</td>
    </tr>
    <tr>
      <td>Title: Third</td>
      <td>number: 3</td>
    </tr>
    <tr>
      <td>Title: Second</td>
      <td>number: 2</td>
    </tr>
    <tr>
      <td>Title: First</td>
      <td>number: 1</td>
    </tr>
    <tr>
      <td>Title: Zero</td>
      <td>number: 0</td>
    </tr>
  </tbody>
</table>
}}}

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

{{{#!highlight python
>>> sorterRequest = TestRequest(form={'table-sortOn': 'table-number-1',
... 'table-sortOrder':'descending'})
}}}

и еще раз обновим и отобразим. Как видите, новая таблица отсортирована по втором столбце и упорядоченна в инверсном порядке:

{{{#highlight python
>>> requestSortedTable = SortingTable(folder, sorterRequest)
>>> requestSortedTable.update()
>>> print requestSortedTable.render()
}}}
{{{#!highlight html
<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Number</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Title: Fourth</td>
      <td>number: 4</td>
    </tr>
    <tr>
      <td>Title: Third</td>
      <td>number: 3</td>
    </tr>
    <tr>
      <td>Title: Second</td>
      <td>number: 2</td>
    </tr>
    <tr>
      <td>Title: First</td>
      <td>number: 1</td>
    </tr>
    <tr>
      <td>Title: Zero</td>
      <td>number: 0</td>
    </tr>
  </tbody>
</table>
}}}

= Настройка таблицы на базе класса =

Есть еще один красивый способ определить строки таблицы на уровне класса. Мы предлагаем метод, который вы можете использовать, если нужно определить несколько колонок, - это метод '''addColumn'''. Перед тем, как определить таблицу, давайте определим метод отображения ячеек:

{{{#!highlight python
>>> def headCellRenderer():
... return u'My items'
>>> def cellRenderer(item):
... return u'%s item' % item.title
}}}

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

{{{#!highlight python
>>> class AddColumnTable(table.Table):
...
... cssClasses = {'table': 'table',
... 'thead': 'thead',
... 'tbody': 'tbody',
... 'th': 'th',
... 'tr': 'tr',
... 'td': 'td'}
...
... cssClassEven = u'even'
... cssClassOdd = u'odd'
...
... def setUpColumns(self):
... return [
... column.addColumn(self, TitleColumn, u'title',
... cellRenderer=cellRenderer,
... headCellRenderer=headCellRenderer,
... weight=1, colspan=0),
... column.addColumn(self, SimpleColumn, name=u'simple',
... weight=2, header=u'The second column',
... cssClasses = {'th':'thCol', 'td':'tdCol'})
... ]
>>> addColumnTable = AddColumnTable(folder, request)
>>> addColumnTable.update()
>>> print addColumnTable.render()
}}}
{{{#!highlight html
<table class="table">
  <thead class="thead">
    <tr class="tr">
      <th class="th">My items</th>
      <th class="thCol th">The second column</th>
    </tr>
  </thead>
  <tbody class="tbody">
    <tr class="even tr">
      <td class="td">First item</td>
      <td class="tdCol td">First</td>
    </tr>
    <tr class="odd tr">
      <td class="td">Fourth item</td>
      <td class="tdCol td">Fourth</td>
    </tr>
    <tr class="even tr">
      <td class="td">Second item</td>
      <td class="tdCol td">Second</td>
    </tr>
    <tr class="odd tr">
      <td class="td">Third item</td>
      <td class="tdCol td">Third</td>
    </tr>
    <tr class="even tr">
      <td class="td">Zero item</td>
      <td class="tdCol td">Zero</td>
    </tr>
  </tbody>
</table>
}}}

Как видите, столбцы таблицы предоставляют все атрибуты, которые были установлены в методе '''addColumn''':

{{{#!highlight python
>>> titleColumn = addColumnTable.rows[0][0][1]
>>> titleColumn
<TitleColumn u'title'>
>>> titleColumn.__name__
u'title'
>>> titleColumn.__parent__
<AddColumnTable None>
>>> titleColumn.colspan
0
>>> titleColumn.weight
1
>>> titleColumn.header
u'Title'
>>> titleColumn.cssClasses
{}
}}}

и вторая колонка

{{{#!highlight python
>>> simpleColumn = addColumnTable.rows[0][1][1]
>>> simpleColumn
<SimpleColumn u'simple'>
>>> simpleColumn.__name__
u'simple'
>>> simpleColumn.__parent__
<AddColumnTable None>
>>> simpleColumn.colspan
0
>>> simpleColumn.weight
2
>>> simpleColumn.header
u'The second column'
>>> simpleColumn.cssClasses
{'td': 'tdCol', 'th': 'thCol'}
}}}

= Порционное отображение данных =

Таблица реализует концепцию порционного отображения данных из коробки. Если количество строк меньше заданного размера '''startBatchingAt''', таблица начнет разбиваться на этом размере. Давайте определим новую таблицу:

Следующим шагом идет настройка провайдера порций. Посмотрите раздел '''!BatchProvider''' (ниже) для получения более подробной информации о порционном отображении данных:

{{{#!highlight python
>>> from zope.configuration.xmlconfig import XMLConfig
>>> import zope.app.component
>>> import z3c.table
>>> XMLConfig('meta.zcml', zope.component)()
>>> XMLConfig('configure.zcml', z3c.table)()
>>> class BatchingTable(table.Table):
...
... def setUpColumns(self):
... return [
... column.addColumn(self, TitleColumn, u'title',
... cellRenderer=cellRenderer,
... headCellRenderer=headCellRenderer,
... weight=1),
... column.addColumn(self, NumberColumn, name=u'number',
... weight=2, header=u'Number')
... ]
}}}

Теперь мы можем создать таблицу:

{{{#!highlight python
>>> batchingTable = BatchingTable(folder, request)
}}}

Нам также следует дать таблице расположение и имя, как принято при траверсинге:

{{{#!highlight python
>>> batchingTable.__parent__ = folder
>>> batchingTable.__name__ = u'batchingTable.html'
}}}

Теперь добавим еще несколько элементов в папку:

{{{#!highlight python
>>> folder[u'sixth'] = File('Sixth', 6)
>>> folder[u'seventh'] = File('Seventh', 7)
>>> folder[u'eighth'] = File('Eighth', 8)
>>> folder[u'ninth'] = File('Ninth', 9)
>>> folder[u'tenth'] = File('Tenth', 10)
>>> folder[u'eleventh'] = File('Eleventh', 11)
>>> folder[u'twelfth '] = File('Twelfth', 12)
>>> folder[u'thirteenth'] = File('Thirteenth', 13)
>>> folder[u'fourteenth'] = File('Fourteenth', 14)
>>> folder[u'fifteenth '] = File('Fifteenth', 15)
>>> folder[u'sixteenth'] = File('Sixteenth', 16)
>>> folder[u'seventeenth'] = File('Seventeenth', 17)
>>> folder[u'eighteenth'] = File('Eighteenth', 18)
>>> folder[u'nineteenth'] = File('Nineteenth', 19)
>>> folder[u'twentieth'] = File('Twentieth', 20)
}}}

Теперь покажем полную таблицу:

{{{#!highlight html
>>> batchingTable.update()
>>> print batchingTable.render()
<table>
  <thead>
    <tr>
      <th>My items</th>
      <th>Number</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Eighteenth item</td>
      <td>number: 18</td>
    </tr>
    <tr>
      <td>Eighth item</td>
      <td>number: 8</td>
    </tr>
    <tr>
      <td>Eleventh item</td>
      <td>number: 11</td>
    </tr>
    <tr>
      <td>Fifteenth item</td>
      <td>number: 15</td>
    </tr>
    <tr>
      <td>First item</td>
      <td>number: 1</td>
    </tr>
    <tr>
      <td>Fourteenth item</td>
      <td>number: 14</td>
    </tr>
    <tr>
      <td>Fourth item</td>
      <td>number: 4</td>
    </tr>
    <tr>
      <td>Nineteenth item</td>
      <td>number: 19</td>
    </tr>
    <tr>
      <td>Ninth item</td>
      <td>number: 9</td>
    </tr>
    <tr>
      <td>Second item</td>
      <td>number: 2</td>
    </tr>
    <tr>
      <td>Seventeenth item</td>
      <td>number: 17</td>
    </tr>
    <tr>
      <td>Seventh item</td>
      <td>number: 7</td>
    </tr>
    <tr>
      <td>Sixteenth item</td>
      <td>number: 16</td>
    </tr>
    <tr>
      <td>Sixth item</td>
      <td>number: 6</td>
    </tr>
    <tr>
      <td>Tenth item</td>
      <td>number: 10</td>
    </tr>
    <tr>
      <td>Third item</td>
      <td>number: 3</td>
    </tr>
    <tr>
      <td>Thirteenth item</td>
      <td>number: 13</td>
    </tr>
    <tr>
      <td>Twelfth item</td>
      <td>number: 12</td>
    </tr>
    <tr>
      <td>Twentieth item</td>
      <td>number: 20</td>
    </tr>
    <tr>
      <td>Zero item</td>
      <td>number: 0</td>
    </tr>
  </tbody>
</table>
}}}

Как видите, таблица неупорядоченная и отображает все элементы. Если мы хотим разбить ее на порции, нам следует установить размер '''startBatchingAt''' меньшим, нежели он установлен по умолчанию. Значение по умолчанию - 50:

{{{#!highlight python
>>> batchingTable.startBatchingAt
50
}}}

Мы начнем порцию на 5 элементе. Это значит, что первые 5 элементов не будут использоваться:

{{{#!highlight python
>>> batchingTable.startBatchingAt = 5
>>> batchingTable.startBatchingAt
5
}}}

Есть также значение '''batchSize''', которое следует установить в 5. По умолчанию, значение этого атрибута - 50:

{{{#!highlight python
>>> batchingTable.batchSize
50
>>> batchingTable.batchSize = 5
>>> batchingTable.batchSize
5
}}}

Теперь мы можем обновить и отобразить таблицу снова. Вы увидите только 5 строк таблицы, и это правильно. Порядок не зависит от чисел, которы мы видим в ячейках:

{{{#!highlight python
>>> batchingTable.update()
>>> print batchingTable.render()
}}}
{{{#!highlight html
<table>
  <thead>
    <tr>
      <th>My items</th>
      <th>Number</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Eighteenth item</td>
      <td>number: 18</td>
    </tr>
    <tr>
      <td>Eighth item</td>
      <td>number: 8</td>
    </tr>
    <tr>
      <td>Eleventh item</td>
      <td>number: 11</td>
    </tr>
    <tr>
      <td>Fifteenth item</td>
      <td>number: 15</td>
    </tr>
    <tr>
      <td>First item</td>
      <td>number: 1</td>
    </tr>
  </tbody>
</table>
}}}

Я думаю нам следует сортировать таблицу по второму столбцу перед тем, как показывать следующие порции данных. Делаем мы это просто установив '''defaultSortOn''':

{{{#!highlight python
>>> batchingTable.sortOn = u'table-number-1'
}}}

Теперь мы должны увидеть хорошо отсортированную таблицу:

{{{#!highlight python
>>> batchingTable.update()
>>> print batchingTable.render()
}}}
{{{#!highlight html
<table>
  <thead>
    <tr>
      <th>My items</th>
      <th>Number</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Zero item</td>
      <td>number: 0</td>
    </tr>
    <tr>
      <td>First item</td>
      <td>number: 1</td>
    </tr>
    <tr>
      <td>Second item</td>
      <td>number: 2</td>
    </tr>
    <tr>
      <td>Third item</td>
      <td>number: 3</td>
    </tr>
    <tr>
      <td>Fourth item</td>
      <td>number: 4</td>
    </tr>
  </tbody>
</table>
}}}

Концепция порций позволяет нам выбрать со всех порций только тот набор данных, который нам нужен. Сделать это мы можем установив любую порцию как строки. Как видите, у нас есть 4 строковые порции данных:

{{{#!highlight python
>>> len(batchingTable.rows.batches)
4
}}}

Мы можем устанавливать такие порции как строки, тогда данные этот порции будут использоваться при отображении. Но будьте внимательны, так как если мы обновим таблицу, наши строки перекроют и обнулят предыдущие значения. Это значит, что вы можете установить любую порцию как строковые данные и отобразить только их. Это доступно, так как метод '''update''' отсортировал все элементы и порцию, которая содержит данные готовые для использования. Эта концепция очень важна, если вам нужно кешировать порции данных.

{{{#!highlight python
>>> batchingTable.rows = batchingTable.rows.batches[1]
>>> print batchingTable.render()
}}}
{{{#!highlight html
<table>
  <thead>
    <tr>
      <th>My items</th>
      <th>Number</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Sixth item</td>
      <td>number: 6</td>
    </tr>
    <tr>
      <td>Seventh item</td>
      <td>number: 7</td>
    </tr>
    <tr>
      <td>Eighth item</td>
      <td>number: 8</td>
    </tr>
    <tr>
      <td>Ninth item</td>
      <td>number: 9</td>
    </tr>
    <tr>
      <td>Tenth item</td>
      <td>number: 10</td>
    </tr>
  </tbody>
</table>
}}}

Как говорилось выше, если вы вызываете метод '''update''' наши настройки порций строк обнуляются:

{{{#!highlight python
>>> batchingTable.update()
>>> print batchingTable.render()
}}}
{{{#!highlight html
<table>
  <thead>
    <tr>
      <th>My items</th>
      <th>Number</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Zero item</td>
      <td>number: 0</td>
    </tr>
    <tr>
      <td>First item</td>
      <td>number: 1</td>
    </tr>
    <tr>
      <td>Second item</td>
      <td>number: 2</td>
    </tr>
    <tr>
      <td>Third item</td>
      <td>number: 3</td>
    </tr>
    <tr>
      <td>Fourth item</td>
      <td>number: 4</td>
    </tr>
  </tbody>
</table>
}}}

Это значит, что, возможно, вы можете обновить все порции, закешировать их, и использовать позже, но, обычно, это не очень полезно, так как нужно проектировать страницу специально для таких нужд. Это также значит, что должен быть еще один путь установить индекс порций данных. И он существует. Есть два других способа, с помощью которых можно установить положение порции. Мы можем установить положение порции с помощью значения '''batchStart''' в таблице, или использовать переменную запроса. Давайте сначала посмотрим первый вариант:

{{{#!highlight python
>>> batchingTable.batchStart = 6
>>> batchingTable.update()
>>> print batchingTable.render()
}}}
{{{#!highlight html
<table>
  <thead>
    <tr>
      <th>My items</th>
      <th>Number</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Seventh item</td>
      <td>number: 7</td>
    </tr>
    <tr>
      <td>Eighth item</td>
      <td>number: 8</td>
    </tr>
    <tr>
      <td>Ninth item</td>
      <td>number: 9</td>
    </tr>
    <tr>
      <td>Tenth item</td>
      <td>number: 10</td>
    </tr>
    <tr>
      <td>Eleventh item</td>
      <td>number: 11</td>
    </tr>
  </tbody>
</table>
}}}

Мы также можем установить положение порции используя значение '''batchStart''' из запроса. Заметьте, что нам нужен префикс таблицы и '''_ _name_ _''' колонки, как было в случае с сортировкой:

{{{#!highlight python
>>> batchingRequest = TestRequest(form={'table-batchStart': '11',
... 'table-batchSize': '5',
... 'table-sortOn': 'table-number-1'})
>>> requestBatchingTable = BatchingTable(folder, batchingRequest)
}}}

Нам также потребуется предоставить таблице положение (location) и имя, как обычно делается с траверсингом:

{{{#!highlight python
>>> requestBatchingTable.__parent__ = folder
>>> requestBatchingTable.__name__ = u'requestBatchingTable.html'
}}}

{{{#!wiki note
Наша таблица должна начать делать порции по меньшему количеству элементов, нежели у нас есть по умолчанию, в противном случае мы не получим порции.
}}}

{{{#!highlight python
>>> requestBatchingTable.startBatchingAt = 5
>>> requestBatchingTable.update()
>>> print requestBatchingTable.render()
}}}
{{{#!highlight html
<table>
  <thead>
    <tr>
      <th>My items</th>
      <th>Number</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Twelfth item</td>
      <td>number: 12</td>
    </tr>
    <tr>
      <td>Thirteenth item</td>
      <td>number: 13</td>
    </tr>
    <tr>
      <td>Fourteenth item</td>
      <td>number: 14</td>
    </tr>
    <tr>
      <td>Fifteenth item</td>
      <td>number: 15</td>
    </tr>
    <tr>
      <td>Sixteenth item</td>
      <td>number: 16</td>
    </tr>
  </tbody>
</table>
}}}

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

z3c.table - продвинутые таблицы

Z3C Table

Цель, которую преследует пакет z3c.table - предложить модульную библиотеку для отрисовки таблиц. Мы используем шаблон "контент провайдер" с колонками, реализованными как адаптеры. Такой подход - мощная базовая концепция.

Важные требования

  • разделение реализации в обновлении частей отрисовки. Такой подход позволяет манипулировать данными после обновления, и перед отрисовкой.
  • мы можем использовать шаблоны страниц, если потребуется, но, по умолчанию, все делается в Python.
  • мы может отрисовывать порционную навигацию отдельно от самой таблицы.

Никаких скинов

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

Заметка

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

Пример установки данных

Таблицы часто используются для отображения нормализованных порций данных. Например, нам необходимо отобразить информацию о файлах в папке. Каждый файл имеет заголовок, размер и тип. Наша таблица должна иметь строку для каждого файла и столбец для этих трех полей данных (заголовка, размера, и типа). Контекстом таблицы всегда является структура данных, по которой можно выполнить итерацию, при этом каждый элемент последовательности соответствует строке таблицы. Давайте создадим папку, которую мы сможем использовать как контекст:

   1 >>> from zope.app.container import btree
   2 >>> class Folder(btree.BTreeContainer):
   3 ...     """Sample folder."""
   4 ...     __name__ = u'folder'
   5 >>> folder = Folder()

XXX: не уверен, куда нам нужно положить эту папку. Также нам можно и не давать значение атрибуту _ _name_ _. Нам не нужно куда либо помещать ее. Давайте установим родительский элемент для папки:

   1 >>> root['folder'] = folder

Теперь создадим простой объект File для наполнения созданной нами папки.

   1 >>> class File(object):
   2 ...     """Sample file."""
   3 ...     def __init__(self, title, size, type=None):
   4 ...         self.title = title
   5 ...         self.number = size
   6 ...         self.type = type

Теперь давайте наполним созданную папку файлами.

   1 >>> folder[u'first'] = File('First', 1)
   2 >>> folder[u'second'] = File('Second', 2)
   3 >>> folder[u'third'] = File('Third', 3)

Создание таблиц

Теперь, когда у нас есть тестовые данные, с которыми можно работать, мы может создать таблицу. Так как таблицы - компоненты пользовательского интерфейса, они требуют и контекста и запроса (request). Они передаются как аргументы конструктору класса Table.

   1 >>> from zope.publisher.browser import TestRequest
   2 >>> from z3c.table import table
   3 >>> request = TestRequest()
   4 >>> plainTable = table.Table(folder, request)

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

   1 >>> plainTable.update()
   2 >>> plainTable.render()
   3 u''

Также стоит заметить, что класс Table - реализация интерфейса ITable. Намного интереснее взглянуть, что предоставляет ITable, когда у нас есть несколько столбцов:

   1 >>> from z3c.table import interfaces
   2 >>> from zope.interface.verify import verifyObject
   3 >>> verifyObject(interfaces.ITable, plainTable)
   4 True

Создание столбцов

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

   1 >>> from z3c.table import column
   2 >>> class TitleColumn(column.Column):
   3 ...
   4 ...     weight = 10
   5 ...     header = u'Title'
   6 ...
   7 ...     def renderCell(self, item):
   8 ...         return u'Title: %s' % item.title

Атрибут header - это текст, который мы можем увидеть в заголовке таблицы, обычно это тег <th>. Атрибут weight указывает порядок колонки в таблице относительно других колонок. Метод renderCell делает всю работу по отображению, возвращая html структуру, которая будет находится внутри колонки таблица, обычно это тег <td>. Метод renderCell должен предоставляться потомками класса Column.

Добавление колонки в таблицу с использованием адаптеров

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

   1 >>> import zope.component
   2 >>> zope.component.provideAdapter(TitleColumn,
   3 ...     (None, None, interfaces.ITable), provides=interfaces.IColumn,
   4 ...      name='firstColumn')

Теперь отрисуем таблицу еще раз:

   1 >>> plainTable.update()
   2 >>> print plainTable.render()

   1 <table>
   2   <thead>
   3     <tr>
   4       <th>Title</th>
   5     </tr>
   6   </thead>
   7   <tbody>
   8     <tr>
   9       <td>Title: First</td>
  10     </tr>
  11     <tr>
  12       <td>Title: Second</td>
  13     </tr>
  14     <tr>
  15       <td>Title: Third</td>
  16     </tr>
  17   </tbody>
  18 </table>

Мы также можем использовать предопределенное имя столбца:

   1 >>> zope.component.provideAdapter(column.NameColumn,
   2 ...     (None, None, interfaces.ITable), provides=interfaces.IColumn,
   3 ...      name='secondColumn')

Теперь мы получим еще один дополнительный столбец:

   1 >>> plainTable.update()
   2 >>> print plainTable.render()

   1 <table>
   2   <thead>
   3     <tr>
   4       <th>Name</th>
   5       <th>Title</th>
   6     </tr>
   7   </thead>
   8   <tbody>
   9     <tr>
  10       <td>first</td>
  11       <td>Title: First</td>
  12     </tr>
  13     <tr>
  14       <td>second</td>
  15       <td>Title: Second</td>
  16     </tr>
  17     <tr>
  18       <td>third</td>
  19       <td>Title: Third</td>
  20     </tr>
  21   </tbody>
  22 </table>

Объединение ячеек

Теперь давайте посмотрим, как можно сделать объединение ячеек для столбца:

   1 >>> class ColspanColumn(column.NameColumn):
   2 ...
   3 ...     weight = 999
   4 ...
   5 ...     def getColspan(self, item):
   6 ...         # colspan condition
   7 ...         if item.__name__ == 'first':
   8 ...             return 2
   9 ...         else:
  10 ...             return 0
  11 ...
  12 ...     def renderHeadCell(self):
  13 ...         return u'Colspan'
  14 ...
  15 ...     def renderCell(self, item):
  16 ...         return u'colspan: %s' % item.title

Теперь зарегистрируем адаптер этого столбца как colspanColumn:

   1 >>> zope.component.provideAdapter(ColspanColumn,
   2 ...     (None, None, interfaces.ITable), provides=interfaces.IColumn,
   3 ...      name='colspanColumn')

Теперь вы видите, насколько объединение через ColspanAdapter больше, чем столбцы таблицы. Такой код вызовет исключение ValueError:

   1 >>> plainTable.update()
   2 ...
   3 ValueError: Colspan for column '<ColspanColumn u'colspanColumn'>' larger then table.

Но если мы установим столбец первой строчкой, таблица отрисуется корректно:

   1 >>> class CorrectColspanColumn(ColspanColumn):
   2 ...     """Colspan with correct weight."""
   3 ...
   4 ...     weight = 0

Зарегистрируйте и отрисуйте таблицу еще раз:

   1 >>> zope.component.provideAdapter(CorrectColspanColumn,
   2 ...     (None, None, interfaces.ITable), provides=interfaces.IColumn,
   3 ...      name='colspanColumn')
   4 >>> plainTable.update()
   5 >>> print plainTable.render()

   1 <table>
   2   <thead>
   3     <tr>
   4       <th>Colspan</th>
   5       <th>Name</th>
   6       <th>Title</th>
   7     </tr>
   8   </thead>
   9   <tbody>
  10     <tr>
  11       <td colspan="2">colspan: First</td>
  12       <td>Title: First</td>
  13     </tr>
  14     <tr>
  15       <td>colspan: Second</td>
  16       <td>second</td>
  17       <td>Title: Second</td>
  18     </tr>
  19     <tr>
  20       <td>colspan: Third</td>
  21       <td>third</td>
  22       <td>Title: Third</td>
  23     </tr>
  24   </tbody>
  25 </table>

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

Существующая реализация позволяет определять таблицу в классе без использования адаптеров.

Сначала нужно определить столбец, который сможет отобразить значения наших элементов:

   1 >>> class SimpleColumn(column.Column):
   2 ...
   3 ...     weight = 0
   4 ...
   5 ...     def renderCell(self, item):
   6 ...         return item.title

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

   1 >>> class PrivateTable(table.Table):
   2 ...
   3 ...     def setUpColumns(self):
   4 ...         firstColumn = TitleColumn(self.context, self.request, self)
   5 ...         firstColumn.__name__ = u'title'
   6 ...         firstColumn.weight = 1
   7 ...         secondColumn = SimpleColumn(self.context, self.request, self)
   8 ...         secondColumn.__name__ = u'simple'
   9 ...         secondColumn.weight = 2
  10 ...         secondColumn.header = u'The second column'
  11 ...         return [secondColumn, firstColumn]

Теперь мы можете создавать, обновлять и отображать таблицу:

   1 >>> privateTable = PrivateTable(folder, request)
   2 >>> privateTable.update()
   3 >>> print privateTable.render()

   1 <table>
   2   <thead>
   3     <tr>
   4       <th>Title</th>
   5       <th>The second column</th>
   6     </tr>
   7   </thead>
   8   <tbody>
   9     <tr>
  10       <td>Title: First</td>
  11       <td>First</td>
  12     </tr>
  13     <tr>
  14       <td>Title: Second</td>
  15       <td>Second</td>
  16     </tr>
  17     <tr>
  18       <td>Title: Third</td>
  19       <td>Third</td>
  20     </tr>
  21   </tbody>
  22 </table>

Каскадные таблицы стилей

Наша реализация таблицы и столбца поддерживает установку классов css. Давайте определим таблицу и столбцы с некими значениями css:

   1 >>> class CSSTable(table.Table):
   2 ...
   3 ...     cssClasses = {'table': 'table',
   4 ...                   'thead': 'thead',
   5 ...                   'tbody': 'tbody',
   6 ...                   'th': 'th',
   7 ...                   'tr': 'tr',
   8 ...                   'td': 'td'}
   9 ...
  10 ...     def setUpColumns(self):
  11 ...         firstColumn = TitleColumn(self.context, self.request, self)
  12 ...         firstColumn.__name__ = u'title'
  13 ...         firstColumn.__parent__ = self
  14 ...         firstColumn.weight = 1
  15 ...         firstColumn.cssClasses = {'th':'thCol', 'td':'tdCol'}
  16 ...         secondColumn = SimpleColumn(self.context, self.request, self)
  17 ...         secondColumn.__name__ = u'simple'
  18 ...         secondColumn.__parent__ = self
  19 ...         secondColumn.weight = 2
  20 ...         secondColumn.header = u'The second column'
  21 ...         return [secondColumn, firstColumn]

Теперь посмотрит, как отображается такая таблица с учетом присвоенных значений css классов. Заметьте, что th и td получили из таблицы и из колонки.

   1 >>> cssTable = CSSTable(folder, request)
   2 >>> cssTable.update()
   3 >>> print cssTable.render()

   1 <table class="table">
   2   <thead class="thead">
   3     <tr class="tr">
   4       <th class="thCol th">Title</th>
   5       <th class="th">The second column</th>
   6     </tr>
   7   </thead>
   8   <tbody class="tbody">
   9     <tr class="tr">
  10       <td class="tdCol td">Title: First</td>
  11       <td class="td">First</td>
  12     </tr>
  13     <tr class="tr">
  14       <td class="tdCol td">Title: Second</td>
  15       <td class="td">Second</td>
  16     </tr>
  17     <tr class="tr">
  18       <td class="tdCol td">Title: Third</td>
  19       <td class="td">Third</td>
  20     </tr>
  21   </tbody>
  22 </table>

Перемежающиеся таблицы

Мы поддерживаем встроенную поддержку перемежающихся строк таблицы, которые основаны на парных и непарных классах CSS. Давайте определим таблицу, включая другие CSS классы. Для поддержки парности/непарности нам следует определить классу cssClassEven и cssClassOdd CSS:

   1 >>> class AlternatingTable(table.Table):
   2 ...
   3 ...     cssClasses = {'table': 'table',
   4 ...                   'thead': 'thead',
   5 ...                   'tbody': 'tbody',
   6 ...                   'th': 'th',
   7 ...                   'tr': 'tr',
   8 ...                   'td': 'td'}
   9 ...
  10 ...     cssClassEven = u'even'
  11 ...     cssClassOdd = u'odd'
  12 ...
  13 ...     def setUpColumns(self):
  14 ...         firstColumn = TitleColumn(self.context, self.request, self)
  15 ...         firstColumn.__name__ = u'title'
  16 ...         firstColumn.__parent__ = self
  17 ...         firstColumn.weight = 1
  18 ...         firstColumn.cssClasses = {'th':'thCol', 'td':'tdCol'}
  19 ...         secondColumn = SimpleColumn(self.context, self.request, self)
  20 ...         secondColumn.__name__ = u'simple'
  21 ...         secondColumn.__parent__ = self
  22 ...         secondColumn.weight = 2
  23 ...         secondColumn.header = u'The second column'
  24 ...         return [secondColumn, firstColumn]

Теперь обновите и отобразите новую таблицу. Как видите, к данному tr классу добавились дополнительные классы even и odd:

   1 >>> alternatingTable = AlternatingTable(folder, request)
   2 >>> alternatingTable.update()
   3 >>> print alternatingTable.render()

   1 <table class="table">
   2   <thead class="thead">
   3     <tr class="tr">
   4       <th class="thCol th">Title</th>
   5       <th class="th">The second column</th>
   6     </tr>
   7   </thead>
   8   <tbody class="tbody">
   9     <tr class="even tr">
  10       <td class="tdCol td">Title: First</td>
  11       <td class="td">First</td>
  12     </tr>
  13     <tr class="odd tr">
  14       <td class="tdCol td">Title: Second</td>
  15       <td class="td">Second</td>
  16     </tr>
  17     <tr class="even tr">
  18       <td class="tdCol td">Title: Third</td>
  19       <td class="td">Third</td>
  20     </tr>
  21   </tbody>
  22 </table>

Сортировка таблицы

Еще одно свойство таблицы - поддержка сортировки данных по столбцам. Так как сортировка данных таблицы вещь очень важная, мы предлагаем ее по умолчанию. Но ее можно использовать только тогда, когда установлено значение sortOn. Вы можете установить это значение на уровне класса, добавляя значение defaultSortOn или устанавливая его как значение в запросе. Мы покажем вам, как это сделать позже. Нам также потребуются столбцы, которые помогут продемонстрировать хороший пример сортировки. Наш новый столбец, по которому будем сортировать, будет использовать атрибут number элементов содержимого в качестве критерия сортировки:

   1 >>> class NumberColumn(column.Column):
   2 ...
   3 ...     header = u'Number'
   4 ...     weight = 20
   5 ...
   6 ...     def getSortKey(self, item):
   7 ...         return item.number
   8 ...
   9 ...     def renderCell(self, item):
  10 ...         return 'number: %s' % item.number

Теперь давайте установим таблицу:

   1 >>> class SortingTable(table.Table):
   2 ...
   3 ...     def setUpColumns(self):
   4 ...         firstColumn = TitleColumn(self.context, self.request, self)
   5 ...         firstColumn.__name__ = u'title'
   6 ...         firstColumn.__parent__ = self
   7 ...         secondColumn = NumberColumn(self.context, self.request, self)
   8 ...         secondColumn.__name__ = u'number'
   9 ...         secondColumn.__parent__ = self
  10 ...         return [firstColumn, secondColumn]

Нам также потребуется больше элементов в контейнере, которые мы будем сортировать:

   1 >>> folder[u'fourth'] = File('Fourth', 4)
   2 >>> folder[u'zero'] = File('Zero', 0)

Давайте отобразим из без установленного значения sortOn:

   1 >>> sortingTable = SortingTable(folder, request)
   2 >>> sortingTable.update()
   3 >>> print sortingTable.render()

   1 <table>
   2   <thead>
   3     <tr>
   4       <th>Title</th>
   5       <th>Number</th>
   6     </tr>
   7   </thead>
   8   <tbody>
   9     <tr>
  10       <td>Title: First</td>
  11       <td>number: 1</td>
  12     </tr>
  13     <tr>
  14       <td>Title: Fourth</td>
  15       <td>number: 4</td>
  16     </tr>
  17     <tr>
  18       <td>Title: Second</td>
  19       <td>number: 2</td>
  20     </tr>
  21     <tr>
  22       <td>Title: Third</td>
  23       <td>number: 3</td>
  24     </tr>
  25     <tr>
  26       <td>Title: Zero</td>
  27       <td>number: 0</td>
  28     </tr>
  29   </tbody>
  30 </table>

Как видите, эта таблица не предоставляет никакого явного порядка. Давайте определим индекс столбца, по которому будем сортировать данные:

   1 >>> sortOnId = sortingTable.rows[0][1][1].id
   2 >>> sortOnId
   3 u'table-number-1'

Теперь давайте используем найденный индекс как значение sortOn:

   1 >>> sortingTable.sortOn = sortOnId

Важным моментом является обновление таблицы после установки сортировки по значению:

   1 >>> sortingTable.update()
   2 >>> print sortingTable.render()

   1 <table>
   2   <thead>
   3     <tr>
   4       <th>Title</th>
   5       <th>Number</th>
   6     </tr>
   7   </thead>
   8   <tbody>
   9     <tr>
  10       <td>Title: Zero</td>
  11       <td>number: 0</td>
  12     </tr>
  13     <tr>
  14       <td>Title: First</td>
  15       <td>number: 1</td>
  16     </tr>
  17     <tr>
  18       <td>Title: Second</td>
  19       <td>number: 2</td>
  20     </tr>
  21     <tr>
  22       <td>Title: Third</td>
  23       <td>number: 3</td>
  24     </tr>
  25     <tr>
  26       <td>Title: Fourth</td>
  27       <td>number: 4</td>
  28     </tr>
  29   </tbody>
  30 </table>

Мы также можем инвертировать порядок сортировки:

   1 >>> sortingTable.sortOrder = 'reverse'
   2 >>> sortingTable.update()
   3 >>> print sortingTable.render()

   1 <table>
   2   <thead>
   3     <tr>
   4       <th>Title</th>
   5       <th>Number</th>
   6     </tr>
   7   </thead>
   8   <tbody>
   9     <tr>
  10       <td>Title: Fourth</td>
  11       <td>number: 4</td>
  12     </tr>
  13     <tr>
  14       <td>Title: Third</td>
  15       <td>number: 3</td>
  16     </tr>
  17     <tr>
  18       <td>Title: Second</td>
  19       <td>number: 2</td>
  20     </tr>
  21     <tr>
  22       <td>Title: First</td>
  23       <td>number: 1</td>
  24     </tr>
  25     <tr>
  26       <td>Title: Zero</td>
  27       <td>number: 0</td>
  28     </tr>
  29   </tbody>
  30 </table>

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

   1 >>> sorterRequest = TestRequest(form={'table-sortOn': 'table-number-1',
   2 ...                                   'table-sortOrder':'descending'})

и еще раз обновим и отобразим. Как видите, новая таблица отсортирована по втором столбце и упорядоченна в инверсном порядке:

{{{#highlight python >>> requestSortedTable = SortingTable(folder, sorterRequest) >>> requestSortedTable.update() >>> print requestSortedTable.render() }}}

   1 <table>
   2   <thead>
   3     <tr>
   4       <th>Title</th>
   5       <th>Number</th>
   6     </tr>
   7   </thead>
   8   <tbody>
   9     <tr>
  10       <td>Title: Fourth</td>
  11       <td>number: 4</td>
  12     </tr>
  13     <tr>
  14       <td>Title: Third</td>
  15       <td>number: 3</td>
  16     </tr>
  17     <tr>
  18       <td>Title: Second</td>
  19       <td>number: 2</td>
  20     </tr>
  21     <tr>
  22       <td>Title: First</td>
  23       <td>number: 1</td>
  24     </tr>
  25     <tr>
  26       <td>Title: Zero</td>
  27       <td>number: 0</td>
  28     </tr>
  29   </tbody>
  30 </table>

Настройка таблицы на базе класса

Есть еще один красивый способ определить строки таблицы на уровне класса. Мы предлагаем метод, который вы можете использовать, если нужно определить несколько колонок, - это метод addColumn. Перед тем, как определить таблицу, давайте определим метод отображения ячеек:

   1 >>> def headCellRenderer():
   2 ...     return u'My items'
   3 >>> def cellRenderer(item):
   4 ...     return u'%s item' % item.title

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

   1 >>> class AddColumnTable(table.Table):
   2 ...
   3 ...     cssClasses = {'table': 'table',
   4 ...                   'thead': 'thead',
   5 ...                   'tbody': 'tbody',
   6 ...                   'th': 'th',
   7 ...                   'tr': 'tr',
   8 ...                   'td': 'td'}
   9 ...
  10 ...     cssClassEven = u'even'
  11 ...     cssClassOdd = u'odd'
  12 ...
  13 ...     def setUpColumns(self):
  14 ...         return [
  15 ...             column.addColumn(self, TitleColumn, u'title',
  16 ...                              cellRenderer=cellRenderer,
  17 ...                              headCellRenderer=headCellRenderer,
  18 ...                              weight=1, colspan=0),
  19 ...             column.addColumn(self, SimpleColumn, name=u'simple',
  20 ...                              weight=2, header=u'The second column',
  21 ...                              cssClasses = {'th':'thCol', 'td':'tdCol'})
  22 ...             ]
  23 >>> addColumnTable = AddColumnTable(folder, request)
  24 >>> addColumnTable.update()
  25 >>> print addColumnTable.render()

   1 <table class="table">
   2   <thead class="thead">
   3     <tr class="tr">
   4       <th class="th">My items</th>
   5       <th class="thCol th">The second column</th>
   6     </tr>
   7   </thead>
   8   <tbody class="tbody">
   9     <tr class="even tr">
  10       <td class="td">First item</td>
  11       <td class="tdCol td">First</td>
  12     </tr>
  13     <tr class="odd tr">
  14       <td class="td">Fourth item</td>
  15       <td class="tdCol td">Fourth</td>
  16     </tr>
  17     <tr class="even tr">
  18       <td class="td">Second item</td>
  19       <td class="tdCol td">Second</td>
  20     </tr>
  21     <tr class="odd tr">
  22       <td class="td">Third item</td>
  23       <td class="tdCol td">Third</td>
  24     </tr>
  25     <tr class="even tr">
  26       <td class="td">Zero item</td>
  27       <td class="tdCol td">Zero</td>
  28     </tr>
  29   </tbody>
  30 </table>

Как видите, столбцы таблицы предоставляют все атрибуты, которые были установлены в методе addColumn:

   1 >>> titleColumn = addColumnTable.rows[0][0][1]
   2 >>> titleColumn
   3 <TitleColumn u'title'>
   4 >>> titleColumn.__name__
   5 u'title'
   6 >>> titleColumn.__parent__
   7 <AddColumnTable None>
   8 >>> titleColumn.colspan
   9 0
  10 >>> titleColumn.weight
  11 1
  12 >>> titleColumn.header
  13 u'Title'
  14 >>> titleColumn.cssClasses
  15 {}

и вторая колонка

   1 >>> simpleColumn = addColumnTable.rows[0][1][1]
   2 >>> simpleColumn
   3 <SimpleColumn u'simple'>
   4 >>> simpleColumn.__name__
   5 u'simple'
   6 >>> simpleColumn.__parent__
   7 <AddColumnTable None>
   8 >>> simpleColumn.colspan
   9 0
  10 >>> simpleColumn.weight
  11 2
  12 >>> simpleColumn.header
  13 u'The second column'
  14 >>> simpleColumn.cssClasses
  15 {'td': 'tdCol', 'th': 'thCol'}

Порционное отображение данных

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

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

   1 >>> from zope.configuration.xmlconfig import XMLConfig
   2 >>> import zope.app.component
   3 >>> import z3c.table
   4 >>> XMLConfig('meta.zcml', zope.component)()
   5 >>> XMLConfig('configure.zcml', z3c.table)()
   6 >>> class BatchingTable(table.Table):
   7 ...
   8 ...     def setUpColumns(self):
   9 ...         return [
  10 ...             column.addColumn(self, TitleColumn, u'title',
  11 ...                              cellRenderer=cellRenderer,
  12 ...                              headCellRenderer=headCellRenderer,
  13 ...                              weight=1),
  14 ...             column.addColumn(self, NumberColumn, name=u'number',
  15 ...                              weight=2, header=u'Number')
  16 ...             ]

Теперь мы можем создать таблицу:

   1 >>> batchingTable = BatchingTable(folder, request)

Нам также следует дать таблице расположение и имя, как принято при траверсинге:

   1 >>> batchingTable.__parent__ = folder
   2 >>> batchingTable.__name__ = u'batchingTable.html'

Теперь добавим еще несколько элементов в папку:

   1 >>> folder[u'sixth'] = File('Sixth', 6)
   2 >>> folder[u'seventh'] = File('Seventh', 7)
   3 >>> folder[u'eighth'] = File('Eighth', 8)
   4 >>> folder[u'ninth'] = File('Ninth', 9)
   5 >>> folder[u'tenth'] = File('Tenth', 10)
   6 >>> folder[u'eleventh'] = File('Eleventh', 11)
   7 >>> folder[u'twelfth '] = File('Twelfth', 12)
   8 >>> folder[u'thirteenth'] = File('Thirteenth', 13)
   9 >>> folder[u'fourteenth'] = File('Fourteenth', 14)
  10 >>> folder[u'fifteenth '] = File('Fifteenth', 15)
  11 >>> folder[u'sixteenth'] = File('Sixteenth', 16)
  12 >>> folder[u'seventeenth'] = File('Seventeenth', 17)
  13 >>> folder[u'eighteenth'] = File('Eighteenth', 18)
  14 >>> folder[u'nineteenth'] = File('Nineteenth', 19)
  15 >>> folder[u'twentieth'] = File('Twentieth', 20)

Теперь покажем полную таблицу:

   1 >>> batchingTable.update()
   2 >>> print batchingTable.render()
   3 <table>
   4   <thead>
   5     <tr>
   6       <th>My items</th>
   7       <th>Number</th>
   8     </tr>
   9   </thead>
  10   <tbody>
  11     <tr>
  12       <td>Eighteenth item</td>
  13       <td>number: 18</td>
  14     </tr>
  15     <tr>
  16       <td>Eighth item</td>
  17       <td>number: 8</td>
  18     </tr>
  19     <tr>
  20       <td>Eleventh item</td>
  21       <td>number: 11</td>
  22     </tr>
  23     <tr>
  24       <td>Fifteenth item</td>
  25       <td>number: 15</td>
  26     </tr>
  27     <tr>
  28       <td>First item</td>
  29       <td>number: 1</td>
  30     </tr>
  31     <tr>
  32       <td>Fourteenth item</td>
  33       <td>number: 14</td>
  34     </tr>
  35     <tr>
  36       <td>Fourth item</td>
  37       <td>number: 4</td>
  38     </tr>
  39     <tr>
  40       <td>Nineteenth item</td>
  41       <td>number: 19</td>
  42     </tr>
  43     <tr>
  44       <td>Ninth item</td>
  45       <td>number: 9</td>
  46     </tr>
  47     <tr>
  48       <td>Second item</td>
  49       <td>number: 2</td>
  50     </tr>
  51     <tr>
  52       <td>Seventeenth item</td>
  53       <td>number: 17</td>
  54     </tr>
  55     <tr>
  56       <td>Seventh item</td>
  57       <td>number: 7</td>
  58     </tr>
  59     <tr>
  60       <td>Sixteenth item</td>
  61       <td>number: 16</td>
  62     </tr>
  63     <tr>
  64       <td>Sixth item</td>
  65       <td>number: 6</td>
  66     </tr>
  67     <tr>
  68       <td>Tenth item</td>
  69       <td>number: 10</td>
  70     </tr>
  71     <tr>
  72       <td>Third item</td>
  73       <td>number: 3</td>
  74     </tr>
  75     <tr>
  76       <td>Thirteenth item</td>
  77       <td>number: 13</td>
  78     </tr>
  79     <tr>
  80       <td>Twelfth item</td>
  81       <td>number: 12</td>
  82     </tr>
  83     <tr>
  84       <td>Twentieth item</td>
  85       <td>number: 20</td>
  86     </tr>
  87     <tr>
  88       <td>Zero item</td>
  89       <td>number: 0</td>
  90     </tr>
  91   </tbody>
  92 </table>

Как видите, таблица неупорядоченная и отображает все элементы. Если мы хотим разбить ее на порции, нам следует установить размер startBatchingAt меньшим, нежели он установлен по умолчанию. Значение по умолчанию - 50:

   1 >>> batchingTable.startBatchingAt
   2 50

Мы начнем порцию на 5 элементе. Это значит, что первые 5 элементов не будут использоваться:

   1 >>> batchingTable.startBatchingAt = 5
   2 >>> batchingTable.startBatchingAt
   3 5

Есть также значение batchSize, которое следует установить в 5. По умолчанию, значение этого атрибута - 50:

   1 >>> batchingTable.batchSize
   2 50
   3 >>> batchingTable.batchSize = 5
   4 >>> batchingTable.batchSize
   5 5

Теперь мы можем обновить и отобразить таблицу снова. Вы увидите только 5 строк таблицы, и это правильно. Порядок не зависит от чисел, которы мы видим в ячейках:

   1 >>> batchingTable.update()
   2 >>> print batchingTable.render()

   1 <table>
   2   <thead>
   3     <tr>
   4       <th>My items</th>
   5       <th>Number</th>
   6     </tr>
   7   </thead>
   8   <tbody>
   9     <tr>
  10       <td>Eighteenth item</td>
  11       <td>number: 18</td>
  12     </tr>
  13     <tr>
  14       <td>Eighth item</td>
  15       <td>number: 8</td>
  16     </tr>
  17     <tr>
  18       <td>Eleventh item</td>
  19       <td>number: 11</td>
  20     </tr>
  21     <tr>
  22       <td>Fifteenth item</td>
  23       <td>number: 15</td>
  24     </tr>
  25     <tr>
  26       <td>First item</td>
  27       <td>number: 1</td>
  28     </tr>
  29   </tbody>
  30 </table>

Я думаю нам следует сортировать таблицу по второму столбцу перед тем, как показывать следующие порции данных. Делаем мы это просто установив defaultSortOn:

   1 >>> batchingTable.sortOn = u'table-number-1'

Теперь мы должны увидеть хорошо отсортированную таблицу:

   1 >>> batchingTable.update()
   2 >>> print batchingTable.render()

   1 <table>
   2   <thead>
   3     <tr>
   4       <th>My items</th>
   5       <th>Number</th>
   6     </tr>
   7   </thead>
   8   <tbody>
   9     <tr>
  10       <td>Zero item</td>
  11       <td>number: 0</td>
  12     </tr>
  13     <tr>
  14       <td>First item</td>
  15       <td>number: 1</td>
  16     </tr>
  17     <tr>
  18       <td>Second item</td>
  19       <td>number: 2</td>
  20     </tr>
  21     <tr>
  22       <td>Third item</td>
  23       <td>number: 3</td>
  24     </tr>
  25     <tr>
  26       <td>Fourth item</td>
  27       <td>number: 4</td>
  28     </tr>
  29   </tbody>
  30 </table>

Концепция порций позволяет нам выбрать со всех порций только тот набор данных, который нам нужен. Сделать это мы можем установив любую порцию как строки. Как видите, у нас есть 4 строковые порции данных:

   1 >>> len(batchingTable.rows.batches)
   2 4

Мы можем устанавливать такие порции как строки, тогда данные этот порции будут использоваться при отображении. Но будьте внимательны, так как если мы обновим таблицу, наши строки перекроют и обнулят предыдущие значения. Это значит, что вы можете установить любую порцию как строковые данные и отобразить только их. Это доступно, так как метод update отсортировал все элементы и порцию, которая содержит данные готовые для использования. Эта концепция очень важна, если вам нужно кешировать порции данных.

   1 >>> batchingTable.rows = batchingTable.rows.batches[1]
   2 >>> print batchingTable.render()

   1 <table>
   2   <thead>
   3     <tr>
   4       <th>My items</th>
   5       <th>Number</th>
   6     </tr>
   7   </thead>
   8   <tbody>
   9     <tr>
  10       <td>Sixth item</td>
  11       <td>number: 6</td>
  12     </tr>
  13     <tr>
  14       <td>Seventh item</td>
  15       <td>number: 7</td>
  16     </tr>
  17     <tr>
  18       <td>Eighth item</td>
  19       <td>number: 8</td>
  20     </tr>
  21     <tr>
  22       <td>Ninth item</td>
  23       <td>number: 9</td>
  24     </tr>
  25     <tr>
  26       <td>Tenth item</td>
  27       <td>number: 10</td>
  28     </tr>
  29   </tbody>
  30 </table>

Как говорилось выше, если вы вызываете метод update наши настройки порций строк обнуляются:

   1 >>> batchingTable.update()
   2 >>> print batchingTable.render()

   1 <table>
   2   <thead>
   3     <tr>
   4       <th>My items</th>
   5       <th>Number</th>
   6     </tr>
   7   </thead>
   8   <tbody>
   9     <tr>
  10       <td>Zero item</td>
  11       <td>number: 0</td>
  12     </tr>
  13     <tr>
  14       <td>First item</td>
  15       <td>number: 1</td>
  16     </tr>
  17     <tr>
  18       <td>Second item</td>
  19       <td>number: 2</td>
  20     </tr>
  21     <tr>
  22       <td>Third item</td>
  23       <td>number: 3</td>
  24     </tr>
  25     <tr>
  26       <td>Fourth item</td>
  27       <td>number: 4</td>
  28     </tr>
  29   </tbody>
  30 </table>

Это значит, что, возможно, вы можете обновить все порции, закешировать их, и использовать позже, но, обычно, это не очень полезно, так как нужно проектировать страницу специально для таких нужд. Это также значит, что должен быть еще один путь установить индекс порций данных. И он существует. Есть два других способа, с помощью которых можно установить положение порции. Мы можем установить положение порции с помощью значения batchStart в таблице, или использовать переменную запроса. Давайте сначала посмотрим первый вариант:

   1 >>> batchingTable.batchStart = 6
   2 >>> batchingTable.update()
   3 >>> print batchingTable.render()

   1 <table>
   2   <thead>
   3     <tr>
   4       <th>My items</th>
   5       <th>Number</th>
   6     </tr>
   7   </thead>
   8   <tbody>
   9     <tr>
  10       <td>Seventh item</td>
  11       <td>number: 7</td>
  12     </tr>
  13     <tr>
  14       <td>Eighth item</td>
  15       <td>number: 8</td>
  16     </tr>
  17     <tr>
  18       <td>Ninth item</td>
  19       <td>number: 9</td>
  20     </tr>
  21     <tr>
  22       <td>Tenth item</td>
  23       <td>number: 10</td>
  24     </tr>
  25     <tr>
  26       <td>Eleventh item</td>
  27       <td>number: 11</td>
  28     </tr>
  29   </tbody>
  30 </table>

Мы также можем установить положение порции используя значение batchStart из запроса. Заметьте, что нам нужен префикс таблицы и _ _name_ _ колонки, как было в случае с сортировкой:

   1 >>> batchingRequest = TestRequest(form={'table-batchStart': '11',
   2 ...                                     'table-batchSize': '5',
   3 ...                                     'table-sortOn': 'table-number-1'})
   4 >>> requestBatchingTable = BatchingTable(folder, batchingRequest)

Нам также потребуется предоставить таблице положение (location) и имя, как обычно делается с траверсингом:

   1 >>> requestBatchingTable.__parent__ = folder
   2 >>> requestBatchingTable.__name__ = u'requestBatchingTable.html'

Наша таблица должна начать делать порции по меньшему количеству элементов, нежели у нас есть по умолчанию, в противном случае мы не получим порции.

   1 >>> requestBatchingTable.startBatchingAt = 5
   2 >>> requestBatchingTable.update()
   3 >>> print requestBatchingTable.render()

   1 <table>
   2   <thead>
   3     <tr>
   4       <th>My items</th>
   5       <th>Number</th>
   6     </tr>
   7   </thead>
   8   <tbody>
   9     <tr>
  10       <td>Twelfth item</td>
  11       <td>number: 12</td>
  12     </tr>
  13     <tr>
  14       <td>Thirteenth item</td>
  15       <td>number: 13</td>
  16     </tr>
  17     <tr>
  18       <td>Fourteenth item</td>
  19       <td>number: 14</td>
  20     </tr>
  21     <tr>
  22       <td>Fifteenth item</td>
  23       <td>number: 15</td>
  24     </tr>
  25     <tr>
  26       <td>Sixteenth item</td>
  27       <td>number: 16</td>
  28     </tr>
  29   </tbody>
  30 </table>

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

Пакеты/ZopePlone/Z3cTable (последним исправлял пользователь RostislavDzinko 2010-07-14 12:32:54)