Common.filter

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

В этой статье идёт речь о стандартизированном фильтре для гридов (на lira-гридах пока не проверялось). --S.gavrilov (обсуждение) 15:08, 19 октября 2016 (UTC) Располагаются функции, обеспечивающие его работу, в грануле common, начиная с версии 5.1, в common.sys\score\common\filtertools. Ниже представлено, как выглядит фильтр с 5 заданными полями.

Filter img.png

Задаётся фильтр (Примеры представлены в разделе User guide/Пример) в три этапа:

1) Добавление записи о карточке-фильтре в файл datapanel.

2) Добавление вызова функции фильтрации в функциях грида (gridMeta/gridData). Все использованные этой функцией данные возвращаются и их можно использовать в дальнейшем для фильтрации или иных целей.

3) Создание карточки-фильтра, представленной функциями создания и сохранения (cardDataFilter/cardDataFilterSave).

В данной статье дано два описания: для человека (user), который будет размещать фильтр и человека (editor), который будет редактировать / улучшать / исправлять фильтр.

User guide.

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

Пример

Первым делом необходимо связать карточки и грид. В приведённом фрагменте специальным для фильтра является лишь объявление карточки example_gridFilter. Применяемый здесь id должен быть эквивалентен filter_id в контексте (задается в cardsave) и вызываемому для проверки в гриде. Сам идентификатор может быть любым, но из неких соображений удобства можно присваивать grid_id + "Filter": example_datapanel.xml

   <datapanel>
   <tab id="1" name="Пример вкладки">
       <element id="example_gridFilter" type="xforms"
          template="filter_card/generalFilterCard.xml"
          proc="grain.xforms.example_card.cardDataFilter.celesta" neverShowInPanel="true">
           <proc id="example_gridFilter_save" name="grain.xforms.example_card.cardDataFilterSave.celesta"
              type="SAVE"/>          
       </element>  
       <element id="example_grid" type="grid" subtype="JS_LIVE_GRID" plugin="liveDGrid"
          proc="grain.grids.example_grid.gridData.celesta">
           <proc id="example_grid_meta" name="grain.grids.example_grid.gridMeta.celesta"
              type="METADATA"/>
           <proc id="example_gridToolbar" name="grain.grids.example_grid.gridToolBar.celesta"
          type="TOOLBAR"/>
       </element>
   </tab>
   </datapanel>

Далее следует в функциях example_grid, gridData и gridMeta (инструкции идентичны), задать создание фильтра текущего грида и, собственно, фильтрацию. Следует дать определение порядку фильтрации полей фильтра, их два: согласно первому, все поля, при объявлении которых ниже не указаны ключи 'unbound' или 'free', подвергаются так называемой "дефолтной фильтрации", в которой разработчик не участвует; согласно второй, при наличии 'unbound' или 'free', фильтрация выполняется самим разработчиком (например, в функции local_filter, как указанов примере). Из особенностей фильтра стоит отметить структуру словаря unbound_dict, который возвращается filter_function (проводит дефолтную фильтрацию) и словарь имеет такой вид:

 имя_поля:{
 'text': приходят значения от полей с текстовым типом, числа и даты, если они введены не в интервале, иначе пустая строка;
 'bool': True или пустая строка;
 'item': возвращается словарь вида {'@id': ид_задающееся_в_селекторе, '@name': название_задающееся_там_же} или пустая строка;
 'condition': словари вида {'@label': название_из_поля_"условие", "@value": одно из значений: equal, right, left, between (каждое из которых либо задается разработчиком, либо соответствует диапазону значений (подробно смотри   в функции condition_constructor в filter.py))};
 'minValue': нижний диапазон интервальных значений (число или дата);
 'maxValue': верхний диапазон интервальных значений (число или дата);
 'items': возвращается список словарей (либо один словарь) вида {'@id': ид_задающееся_в_мультиселекторе, '@name': название_задающееся_там_же (сюда приходит значения для селекторов по дефолту)} или пустая строка;}

Также, стоит упомянуть, что функция add_filter_buttons имеет параметры для указания наименование кнопки, модального окна и его размера в "полях". Подробнее смотри filter_cards_gen.py. А функция is_exist, обитающая в any_functions.py позволяет проверить, существует ли в словаре ключ / путь в словаре и возвращает в случае положительного ответа True, иначе False и None, если пришёл не словарь.

example_grid.py

   def gridData(context, main=None, add=None, filterinfo=None,
            session=None, elementId=None, sortColumnList=None, firstrecord=None, pagesize=None):   
       ...
       # Наименование фильтра в контексте. Желательно делать по примеру
       filter_name = '%sFilter' % elementId # Взято из датапанели
       # Объявление словаря, который содержит данные всех полей (не обращайте внимание на название)
       unbound_dict = {}
       # Проверка на то, что была нажата кнопка "Выполнить"(поиск)
       if filter_name in context.getData().get('card_save', []):
           unbound_dict = filtered_function(context, filter_name, example_cursor)
           # Функция local_filter позволяет производить фильтрацию, независимую от грида
           local_filter(example_cursor, unbound_dict)
       else:
           # Некий набор инструкций, описывающих фильтрацию до того, как будет открыта карточка фильтра
           example_cursor.setFilter("example_field", "filter_condition")
       ...


   def gridToolBar(context, main=None, add=None, filterinfo=None, session=None, elementId=None):
       ...
       filter_name = '%sFilter' % elementId
       data = {"gridtoolbar":{"item":[]}}
       data["gridtoolbar"]["item"].append(add_filter_buttons(filter_name, session, height=8))
       ...


   def local_filter(example_cursor, unbound_dict):
       # Образец для сравнения, возвращает True, если сравнивается с не пустым объектом
       smth = Something()
       # Проверка на существование поля в словаре и значения внутри него.
       if is_exist(unbound_dict, example_field_name, {'path_to': {'unempty_values': smth}}):
           example_cursor.setFilter('example_field_name', '(!null)')

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

Функция filter_assembly располагается в filter.py и её вход служит источником данных для списка полей в context.getData()[filter_name]. Для фильтрации доступны такие способы выбора элементов фильтрации: селектор, мультиселектор, селект1, числовое/текстовое/дата поля для ввода, булёвый чекбокс. Каждый из них как способен отфильтровываться по выбранному значению (или их диапазону), так и просто возвращать введенные данные разработчику.

Из обязательных есть ключи name, label для каждого поля и unbound/free+type для полей, которые разработчик намерен использовать в отрыве от общей фильтрации. Для задания вида фильтрации, отличного от input-полей, необходимо передать один из ключей: select:OrderDict([(возвращаемое_значение, показанное_пользователю)]), selector:True, itemset:True(для мультиселектора).

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

Функция card_info (--S.gavrilov (обсуждение) 09:07, 20 октября 2016 (UTC)) возвращает тот же словарь, который хранится в context.getData()[filter_name], за исключением элементов, которые не показываются (удалены из фильтра путем нажатия на кнопочку справа от поля, либо заданы в filter_assembly как не отображающиеся.

Дальнейший порядок задания настроек карточки не отличается от общепринятого.

example_card.py

   def cardData(context, main, add, filterinfo=None, session=None, elementId=None):
       uПроцедура, описывающая структуру фильтра для карточки телеграмм
       if elementId not in context.getData():
           # Курсор фильтруемой таблицы
           example_cursor = exampleCursor(context)
           # Функция, собирающая данные каждого поля, которое необходимо фильтровать
           filter_assembly(context,
               # Курсор, по которому необходимо фильтровать
               example_cursor, 
               # ID карточки
               elementId,
               # Список с полями фильтрации 
               [
                   # 'name' == имя фильтруемого поля в таблице, 'label' == наименование поля, которое будет показано пользователю, 'default' == значение, подставляемое по умолчанию
                   {   'name': 'planeNumber', 'label': u'Номер ВС (по вхождению)', 'default': ['RA-%']}, 
                   # 'itemset' == следует ли создать мультиселектор по уникальным значениям данного поля
                   {   'name': 'planeTypeName', 'label': u'Вид ВС', 'itemset': True}, 
                   # 'type' == тип данных поля в случае, если 'free': True, что означает независимость данного поля от курсора, 'select_info' == путь к селектору, 'selector' == означает,Ю что по данному полю есть селектор
                   {   'name': 'operator_country', 
                       'label': u'Страна эксплуатанта', 
                       'type': 'TEXT',
                       'free': True,
                       'select_info': 'nsi.selectors.planesAndPlaneTypes.countrySelector', 
                       'selector': True,
                   },
                   # 'especial_conds' == передаем словарь,в  котором указывает ключами один из представленных вариантов (см. в коде) , а значением -- строку, выводимую пользователю
                   {   'name': 'dateTo', 'label': u'Сертификат просрочен', 'especial_conds': {'right': u'к'}},
                   # 'unbound' == обозначает, что данное поле существует в курсоре, но по нему не следует проводить фильтрацию
                   {   'name': 'needCertificate', 'label': u'Нет информации о СЛГ', 'type': 'BOOLEAN', 'unbound': True, 'free': True},
                   # 'select' == передает значения, для отображения в select1
                   {   'name': 'legalEntityId', 'label': u'Пример селекта', 'select': {'le487': u'Первый парень', 'le492': u'Второй парень'}}
               ],
               # Имя гранулы.таблицы
               'nsi.vw_plane')
       # описание данных XML
       _data = {
           "schema": {
               "@xmlns": "",
               "@maxFilters": "0",
               "filters": {"filter": card_info(context, elementId)}
           }
       }
       # Число полей
       _data["schema"]["@maxFilters"] = len(context.getData()[elementId])
       
       _settings = {
           "properties": { 
               "event": [
                   {
                       "@name": "single_click",
                       "@linkId": "1",
                       "action": {"#sorted":
                           [{"main_context": "current"},
                           {"datapanel": {
                               "@type": "current",
                               "@tab": "current",
                               "element": [
                                   {
                                       "@id": "planesGrid",     # ID фильтруемого грида
                                       "add_context": add    # ID фильтра
                                   }
                               ]
                           }}]
                       }
                   }
               ]
           }
       }
       jsonData = XMLJSONConverter.jsonToXml(json.dumps(_data))
       jsonSettings = XMLJSONConverter.jsonToXml(json.dumps(_settings))
       return JythonDTO(jsonData, jsonSettings)
   
   
   def cardDataSave(context, main=None, add=None, filterinfo=None, session=None, elementId=None, xformsdata=None):
       xformsdata = json.loads(xformsdata)    
       # Функция, выполняющая сохранение данных фильтрации
       card_save(xformsdata, context, add)

После создания объектов, приближенных к описываемым, и адаптированных под конкретный случай, должен получится фильтр, похожий на картинку в верху страницы. --S.gavrilov (обсуждение) 09:07, 20 октября 2016 (UTC)

Editor guide.

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

В первый каскад входят функции, которые запускаются один раз: при создании списка в словаре context.getData()[filter_name].

Описание внутренних функций. Первым происходит вызов filter_assembly с некими данными, которые детально описаны выше. В самом теле функции изначально происходит превращение полученного в переменной field_name_list списка в словарь, где ключами служат имена полей. Далее, этот словарь засылается в функцию create_filter_map. В ней из полученного словаря и курсора достаются тип данных поля, вид отображения поля в фильтре и то, будет ли он фильтроваться встроенными функциями фильтрации в гриде. Далее, после получения этих данных для каждого поля фильтра, происходит заполнение словаря (описано ниже, в структуре данных), описывающего поле в фильтре. Этот процесс состоит из 4 этапов:

1) Сначала происходит заполнение переменной future_filter сырыми данными.

2) Вызов функции as_default, внутри которой происходит заполнение данных, переданных в default (очень оригинально).

3) Заполнение ключа 'conditions' в зависимости от типа данных поля и способа его отображения.

4) Добавления поля в список context.getData()[filter_name]

Структура данных. Каждый список в context.getData()[filter_name] состоит из словарей, структура каждого из которых наглядно представлена в filter.filter_assembly в переменной future_filter. Пояснение к аргументам:

 key -- содержит view/unview, которые соответственно определяют, будет ли показано поле при открытии карточки;
 id -- содержит как имена полей таблицы, курсор которой передан в filter_assembly, так и строки-обозначения, которые передаются с параметром free;
 tableName -- приходит как имя_гранулы.имя_таблицы, либо вытягивается из курсора;
 label -- полностью соответствует заданному label;
 type -- тип поля (вытягивается из курсора, либо задается пользователем);
 bound -- содержит одну из трёх строк: bound (обозначает то, что данное поле должно фильтроваться по дефолту, используя функцию filter_function), unbound (данное поле присутствует в курсоре (потому из него можно вытянуть тип данных, 
 но фильтровать его будет пользователь самостоятельно), free (тот же unbound, но с прямым указанием типа);
 face -- обозначения отображения фильтра. Может быть 'itemset'(мультиселектор), 'selector', 'select'(выбор из списка) или 'usuall'(инпуты, булев чекбокс);
 minValue -- содержит нижний диапазон значений ПРИ УСЛОВИИ, что current_condition содержит в '@value' between, либо left;
 maxValue -- повторяет minValue, но содержит верхний диапазон, если в '@value' between, либо right;
 value -- также является контейнером для значений, если current_condition содержит в '@value' equal;
 boolInput -- содержит 'false' или 'true' (пока что, мб заменю на адекватные булевы значения --S.gavrilov (обсуждение) 09:52, 20 октября 2016 (UTC));
 items -- содержит внутри себя словарь с одним ключом 'item', внутри которого список словарей {'@id': вернувшийся_идентификатор, '@name': вернувшееся название} с выбранными в  мультиселекторе значениями;
 selects -- содержит список словарей с данными для показа в селекте;
 item -- содержит 1 словарик {'@id': вернувшийся_идентификатор, '@name': вернувшееся название}, которые заполнены, если данное поле -- селектор и в нем выбрано значение;
 default -- копируется содержимое default, заданное в описании поля;
 selector_data -- содержит путь к постороннему селектору;
 current_condition -- содержит одно из equal, right, left, between в зависимости от текущего выбора / типа данных;
 required -- 'true', если данное поле должно быть обязательно заполнено при задании фильтрации, иначе 'false';
 conditions -- результат выполнения condition_constructor.


--S.gavrilov (обсуждение) 09:52, 20 октября 2016 (UTC)