Lyra

Материал из Course Orchestra
Перейти к: навигация, поиск

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

Технология Lyra работает на стыке Showcase и Celesta, поэтому Lyra не имеет отдельного дистрибутива. Части программного кода Lyra находятся одновременно в Celesta, Showcase и в системной грануле lyra.

Lyra-форма

Lyra-формы могут быть двух основных типов: карточка (Card) и грид (Grid). Каждая форма задаётся путём определения Python-класса, наследованного от одного из базовых (lyra.cardForm.CardForm или lyra.gridForm.GridForm). Сама форма при этом задаётся декларативно и характеризуется:

  • курсором, к которому привязана. Каждая из Lyra-форм обязана переопределять метод _getCursor(self, context), который должен возвращать соответствующий Celesta-курсор. При необходимости, в этом же методе на курсор могут быть наложены фильтры и сортировка.
  • набором связанных и свободных полей, отображаемых на форме.

Lyra берёт на себя задачи передачи информации между формой и сервером, а также навигации по записям курсора. Грид-форма также самостоятельно решает задачу быстрого отображения, скроллирования и позиционирования грида с большим количеством записей. Разработчику решений достаточно декларировать, какие поля он желал бы видеть на форме, в каком порядке и с какими свойствами.

Типы полей

Поля, как уже упоминалось, могут быть двух типов:

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

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

Конструирование формы «с нуля» за пять шагов

Чтобы создать lyra-форму «с нуля», необходимо выполнить следующие шаги:

Шаг 1. Создать Python-класс, унаследованный от CardForm или от GridForm и аннотированный декоратором @form:

@form()
class TestForm(CardForm):

или

@form(profile='default.properties',
      gridwidth="600px",
      gridheight="200px")
class TestForm(GridForm):

Шаг 2. Переопределить метод _getCursor(context) так, чтобы он возвращал отсортированный и отфильтрованный курсор, являющийся в данном контексте источником набора записей для отображения на форме:

    def _getCursor(self, context):
        result = testCursor(context)
        result.setRange('myField', self.myFilterValue)
        return result


Шаг 3 (необязательный). Если на форме нужны свободные поля, то необходимо их определить в виде свойств, аннотированных декоратором @formfield:

    @formfield(celestatype='INT', 
               caption='Подпись поля',
               width=30)
    def myfield(self):
        return self.my

    @myfield.setter
    def myfield(self, value):
        self.my = value

Шаг 4 (необязательный). В CelestaSQL в определении отображаемой в данной форме таблицы необходимо с помощью CelestaDoc задать свойства полей таблицы, которые перенесутся в связанные поля формы. Можно этого и не делать: во-первых, значения свойств, не заданных в CelestaDoc, подставятся по умолчанию (например, свойство caption будет по умолчанию совпадать с именем поля, что зачастую и нужно), а во-вторых, переопределить свойства полей можно во время выполнения непосредственно в классе формы:

create table test (
/**
 {"caption": "Идентификатор"}
 */
id int identity not null primary key,

/**
 {"caption": "Целое значение"}
 */
attrInt int default 3
);

Шаг 5. Определить состав и последовательность полей формы в конструкторе класса формы путём вызова следующих методов:

  • createField(name) — добавляет в форму поле с именем name и возвращает объект типа LyraFormField. Значение name обязано совпадать
    • либо с одним из названий столбца курсора формы (и тогда создаётся связанное поле),
    • либо с именем свойства класса формы, объявленного при помощи декоратора @formfield, и тогда создаётся свободное поле.

У возвращаемого методом createField(name) объекта с типом LyraFormField можно затем поменять свойства!

  • createAllBoundFields(), что эквивалентно вызову метода createField для каждого из полей таблицы.
  • createAllUnboundFields(), что эквивалентно вызову метода createField для каждого из свойств класса, объявленного при помощи декоратора @formfield.

Например, если мы хотим, чтобы сначала в форме шли все свободные, а затем — все связанные поля, при этом нас устраивают значения свойств полей, заданные по умолчанию / в CelestaDoc / в декораторах, то мы можем написать так:

    def __init__(self, context):
        super(TestForm, self).__init__(context)
        self.createAllUnboundFields()
        self.createAllBoundFields()

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

Сценарий Стратегия создания формы
  • Во всём приложении имеется всего одна форма на базе таблицы
  • ИЛИ ЖЕ форм для этой таблицы может быть много, но на данную форму нужно вывести все поля таблицы или представления в соответствии со свойствами, заданными в CelestaDoc.
Следует воспользоваться методом createAllBoundFields(), прописав, при необходимости, в CelestaSQL-скрипте CelestaDoc на поля. В частности, если никакой CelestaDoc не задан, будет построена форма, содержащая все поля таблицы, а в качестве подписей будут использованы названия этих полей, что очень удобно для быстрого конструирования гридов. Поля, у которых на уровне CelestaDoc указано visible=False, не будут отображаться на форме. Для добавления всех свободных полей следует воспользоваться методом createAllUnboundFields().
На форму нужно вывести только очень малую часть полей, либо же сделать это очень специфично, не обращая внимания на то, что указано в CelestaDoc. Следует воспользоваться несколькими вызовами метода createField(name) для каждого из полей. При необходимости, свойства объектов, возвращаемых при вызовах этого метода, можно изменять.
В целом атрибуты, указанные в CelestaDoc, устраивают, но для малой части полей нужно их переопределить. Следует сначала воспользоваться методом createAllBoundFields() для того, чтобы добавить все поля с их атрибутами, взятыми из CelestaDoc, а затем, получив для каждого из полей, которое нужно модифицировать, метаданные при помощи метода getFieldsMeta(...), исправить их прямым присвоением свойств.
Важная информация
Обратите внимание, что, подобно тому, как в таблице имя столбца должно быть уникальным, в пределах формы имя поля тоже должно быть уникальным. Поэтому приведёт к ошибке повторный вызов метода createAllBoundFields(), а также двойной вызов метода createField(name) для одного и того же поля. К ошибке приведёт и задание свободного поля, совпадающего по имени с полем таблицы, добавленным в форму.

Методы _afterReceiving(...) и _beforeSending(...)

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

Основными точками входа в бизнес-логику являются переопределяемые разработчиком решения методы

_afterReceiving(self, c) 
_beforeSending(self, c)

Метод _afterReceiving(self, c) вызывается после получения данных формы с клиента, но перед тем, как данные будут сброшены в базу данных. Таким образом, если в нём поменять поля курсора, то в базу попадут изменённые значения. В аргументе c содержится курсор с полями, пришедшими из формы.

Метод _beforeSending(self, c) вызывается перед отправкой данных на сериализацию и в форму. Таким образом, если в нём поменять поля, на форме отобразятся изменённые значения. В аргументе c содержится курсор с полями, пришедшими из базы данных.

Бизнес-логика может содержаться также в getter-ах и setter-ах свободных полей.

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

Метод _beforeShow(...)

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

Метод get_properties_(self)

Для работы с гридом в Showcase необходимо иметь служебное поле _properties_, не отображаемое пользователю, а содержащее служебные данные Showcase. Метод get_properties_(self) является способом лёгкого определения данного столбца: достаточно определить данный метод в классе формы, и соответствующее поле в форму будет добавлено.

Атрибуты формы

Каждая форма характеризуется набором атрибутов, передаваемых через необязательные именованные параметры декоратора @form:

  • profile — grid.properties файл.
  • gridwidth — ширина грида (в пискелах)
  • gridheight — высота грида (в пикселах)
  • defaultaction — действие по умолчанию (см. справку по Showcase)

Атрибуты полей

Каждое поле формы (экземпляр класса LyraFormField) характеризуется набором атрибутов:

  • type — тип данных. Одно из значений:
    • INT целое значение,
    • REAL числовое значение,
    • VARCHAR строка,
    • BIT битовое поле (используется элемент управления "checkbox"),
    • DATETIME дата (используется элемент управления "календарь").
  • caption — подпись. Отображаемая на поле подпись поля.
  • editable — признак редактируемости. Если значение равно False, то поле только для чтения.
  • visible — признак отображения. Если значение равно False, то поле не отображается на форме.
  • required — признак обязательности заполнения. Внимание: связанные поля, объявленные в базе как not null, всегда будут иметь признак required. Установка этого признака в false любым методом игнорируется.
  • scale — максимальное число десятичных знаков после запятой (актуально для REAL-полей).
  • width — ширина поля для отображения (в пикселях).
  • assist — процедура, отвечающая за отображение формы-помощника выбора значения в поле.

Способы задания свойств полей

Итак, свойства полей форм в Lyra могут быть заданы:

  • В design-time:
    • для связанных полей в CelestaDoc на полях таблиц,
    • для свободных полей в параметрах декоратора @formfield.
  • В run-time: для любых полей во время выполнения путём изменения свойств объекта LyraFormField, полученного либо при вызове метода createField(name), либо путём извлечения из словаря, возвращаемого методом getFieldsMeta().

Чтобы задать атрибуты поля для Lyra в CelestaDoc, необходимо в скрипте CelestaSQL вставить в CelestaDoc объект в формате JSON, например, так:

CREATE TABLE table1 
(
  /** {"caption": "название поля",
       "visible": false}*/
  column1  INT NOT NULL IDENTITY PRIMARY KEY,
  /** игнорируемый текст {"caption": "название поля с \"кавычками\"",
       "editable": false,
       "visible": true} игнорируемый текст*/
  column2  REAL,
  column3 BIT NOT NULL DEFAULT 'FALSE'
 );


Важная информация
Задание атрибутов поля в CelestaDoc удобно тем, что атрибут, заданный в одном месте (в скрипте CelestaSQL) будет по умолчанию использоваться во всех формах, использующих соответствующую таблицу в качестве источника данных. При этом в каждой конкретной форме всегда можно переопределить атрибуты во время выполнения. Если же форма, использующая таблицу, всего одна, то правильным подходом является задание соответствующих атрибутов полей прямо в CelestaDoc. Обратите внимание, что система автоматически выделяет из текста CelestaDoc первый встречающийся JSON-объект, проигнорировав остальное текстовое содержимое, которое также может присутствовать там для других целей.

Декоратор @formfield добавляется к функциям, возвращающим значения свободных полей, и также имеет следующие параметры:

  • type — тип данных поля. Обязательный параметр, т. к. Python не строго типизированный язык и система не может без явного указания определить тип данных свободного поля — а значит, и подходящий тип соответствующего визуального элемента управления на формы.
  • caption, editable, visible и т. д. — необязательные параметры, соответствующие одноименным атрибутам поля.
Свойство Порядок подстановки значений для свободного поля Порядок подстановки значений для связанного поля
type
Обязательный параметр type декоратора @formfield.
Определяется на основании типа данных поля в таблице.
caption
  1. Параметр caption декоратора @formfield,
  2. если не задано, то название функции-геттера.
  1. Celestadoc поля в таблице (атрибут caption),
  2. если не задано, то идентификатор (название) поля в таблице
editable
  1. Параметр editable декоратора @formfield,
  2. иначе True.
  1. Celestadoc поля в таблице (атрибут editable),
  2. если не задано, то True.
visible
  1. Параметр visible декоратора @formfield,
  2. если не задано, то True.
  1. Celestadoc поля в таблице (атрибут visible),
  2. если не задано, то True.

Реализация Lyra-форм

Ниже представлена UML-диаграмма Java-классов системы Lyra:

Lyra.png

От Java-класса BasicGridForm наследуется Python-класс lyra.gridForm.GridForm, от BasicCardForm — lyra.cardForm.CardForm.

Пример реализации класса формы с комментариями

# coding=UTF-8

#импорт базового класса формы
from lyra.cardForm import CardForm
#импорт декораторов @form и @formfield
from lyra.basicForm import form
from lyra.basicForm import formfield
#импорт класса курсора
from _testgrain_orm import testCursor


#декоратор @form ОБЯЗАТЕЛЕН
#класс формы наследуется либо от lyra.cardForm.CardForm, либо от lyra.gridForm.GridForm
@form()
class TestForm(CardForm):
    #конструктор формы вызывается один раз в рамках пользовательской сессии при первом отображении формы  
    #при последующих обращениях к форме в рамках пользовательской сессии объект формы используется повторно, 
    #переменные объекта сохраняются в оперативной памяти сервера
    def __init__(self, context):
        #вызов унаследованного конструктора ОБЯЗАТЕЛЕН
        super(TestForm, self).__init__(context)

        #здесь могут быть определены переменные формы и произведены иные действия, требуемые для инициализации
        self.f1 = 0
        self.f2 = 1
        
        #вызов этого метода приведёт к получению формой ВСЕХ полей курсора в том порядке, в котором они определены в SQL
        #self.createAllBoundFields()
        
        #вместо этого мы явно указываем, какие связанные поля будут входить в форму, переопределяя, по необходимости, их атрибуты и порядок следования 
        f=self.createField('field2')
        f.setCaption('Подпись первого поля')
        f.setEditable(True)

        #значение createField в этот раз мы не обрабатываем, поэтому останутся значения из CelestaDoc или по умолчанию.
        self.createField('field1')

    #метод получения объекта курсора
    def _getCursor(self, context):
        #здесь может быть выполнена сортировка и фильтрация в соответствии с заданными программно или пользователем ограничениями
        return testCursor(context)

    #Метод чтения значения поля объявляется декоратором @formfield с опциональным указанием параметров
    @formfield(celestatype='INT', 
               caption='Поле 1', 
               visible=True)
    def ff1(self):
        return self.f1

    #Метод записи значения поля.
    @ff1.setter
    def ff1(self, value):
        self.f1 = value
        
    @formfield(celestatype='INT', 
               caption='Поле 2')
    def ff2(self):
        return self.f2

    #Метод, вызываемый после десериализации принятых от формы данных
    #Параметр c содержит курсор
    def _afterReceiving(self, c):
        self.f2 = self.f1 * self.f1
    
    #Метод, вызываемый перед сериализацией и передачей данных на форму
    #Параметр c содержит курсор    
    def _beforeSending(self, c):
        pass