Мост между Python и React
Сова – это нано-фреймворк, который можно встроить в другие фреймворки.
Картинка с sova.online, на котором запущено 3 http сервера:
http://sova.online/ — просто Falcon
http://sova.online:8000/ — просто Django
http://sova.online:8001/ — просто Python (логин: 1, пароль: 1)
Там же исходные коды и инструкция по установке. Там же нет рекламы.
Идея делать сайты на Питоне с прорисовкой на React не нова. Есть замечательный фреймворк https://plot.ly/products/dash/, зачем еще что-то делать?
Объясняю: Сова не рассчитана на разработку сайтов. Это инструмент для замены толстых клиентов на приложения, работающие через браузер (десктопные приложения).
— Что-ли веб-клиент?
— Нет. Это не веб-клиент. Это приложение, работающее в браузере.
— Не понимаю.
— К сожалению, и многие разработчики не понимают.
Я как генеральный активно работал с несколькими интернет приложениями.
Онлайн клиент банка Югра (банк закрыт).
Хорошее было приложение, но это был Java applet, т.е. толстый клиент, запускаемый из браузера. И банк Югра, и апплеты в прошлом.
Онлайн клиент банка ВТБ-24 (банк закрыт).
Я гуманист, но поработав с этим чудом, стали появляться жестокие мысли типа: «Заставить разработчика зарегистрировать в нем 1000 платежек».
При этом как веб-клиент он прекрасен. Анимация, открывается на мобильнике. Вау! Круто!
Спросил знакомого бухгалтера: как ты с ним работаешь?
Она говорит: прекрасно! загружаю данные в 1с, в 1с работаю, результаты выгружаю обратно.
Онлайн клиент сбербанка
Удовлетворительный клиент, работать можно. Когда меня попросили его оценить, я поставил ему 3 бала из 5 и дал список замечаний. Это при моих 10 платежках в месяц. Те, кто делает 100 платежек в день, скорее всего, выгружают информацию.
Заполнение платежки.
Зеленая область, занимающая 20% экрана – это меню. Оно не просто мешает (position: fixed), оно говорит о том, что разработчик не профессионал. Если я начал создавать платеж, на экране д.б. 3 кнопки: «Создать», «Сохранить как шаблон», «Отмена». Эти кнопки есть (почему-то внизу). Здесь не multi-page SPA: перейдешь по пункту меню, — данные в форме пропадут.
Тот, кто это делал, даже не поймет, в чем наезд: «Нормально сделано, все так делают, такая библиотека, люди ведь работают …». И он прав. Спрашивать надо с руководителя проекта, а для руководителя главное – это БД и слои в концептуальной модели. А формы – наймем мальчиков, они нарисуют. И ведь гордятся, наверное, этой халтурой.
Торговые площадки (5 штук, 44 ФЗ)
Это действительно приложения (не веб-клиенты). Но контроллеры полей мне не нравятся.
Примеры:
Выровнено странно, ширина поля явно недостаточна, autoheight в поле ввода отсутствует.
Еще пример. В поле «Дата публикации» нет шаблона дд.мм.гггг, календарь с ошибкой, пиктограмма календаря пугает:
Список на rts-tender: есть выделенная цветом текущая запись, стрелками можно двигаться по списку, но нет автоскроллинга (можно убежать за границу экрана), ни Enter, ни пробел не открывают ссылку, табулятор не привязан к текущей записи. Хотя открыть ссылку можно только мышкой, я оцениваю контрол со знаком плюс. Такого функционала (запомнить и подсветить текущий документ) мне не хватает в mail.ru
Вроде как мелочи. Но профессиональное приложение отличается от полупрофессионального именно мелочами. Конечному пользователю наплевать, какая у вас БД и сколько слоев в концептуальной модели. Он работает с экранными формами и у него 3 требования: функционально, удобно, быстро.
К сожалению, выбор системы определяют айтишники и начальники, которые сами с системой не работали и работать не будут. Скорость они оценят, функциональность оценят так, как они это понимают, а на удобство им наплевать, главное, чтобы было красиво.
Павел Валерьевич Дуров не изобретал ни соцсеть, ни мессенджер. Он сделал то, что нужно пользователям, удобно и красиво. И люди это оценили, в том числе и материально.
Сова – это инструмент для построения профессионального интерфейса.
Что это значит, на примере СЭД.
Есть СЭД, в ней 3 группы пользователей:
Начальство
Специалисты, готовящие документы
Делопроизводители.
Начальники, они как дети. Им нужно, чтобы просто и красиво. В идеале 1 кнопка и 1 поле. И чтобы понтов побольше. Веб-клиент и, конечно, мобильный клиент, чтобы понты демонстрировать.
Специалисты. Веб-клиент, помесь соцсеть/почтовик. Не забывайте, что специалистов много и их надо обучать. Чем привычней будет для них среда, тем лучше. Мобильный клиент тоже пригодится, если служба безопасности разрешит.
Делопроизводители. Вот здесь пригодится Сова. Делопроизводители – это системообразующая группа пользователей. Все остальные могут заболеть/уйти в отпуск/перестать пользоваться, — СЭД будет работать. Если остановится регистрация, встанет все.
Делопроизводство – это конвейер, и здесь важно все, любая мелочь: шрифты, полутона, автоматическое заполнение, проверка значений, удобство ввода и т.д.
СЭД «Дело». Кабинеты исполнителей сделаны на веб-клиенте, канцелярия – толстый клиент. Все прекрасно, но это будет работать, пока правительство не запретит Windows в госструктурах. Я люблю Win 7, но если бы правителем был я, it-рынок взбодрился новыми заказами, а MS остался в светлой памяти. Кстати, 6 декабря Антон Силуанов подписал директиву о переходе на отечественное ПО.
Sova.online
Как Сова открывает форму.
Без multi-page.
Центральным элементом Совы является компонент Document. Нажав ctrl-U на стартовой странице, вы увидите все, что нужно для создания объекта класса Document:
— данные полей БД;
— url формы для отображения;
— dbAlias, unid — для работы с БД;
— что-то там еще.
В некоторой степени Document – это аналог Redux-form.
Форма загружается в виде JSON строки, затем бывший словарь становится объектом, имеющим style, className и массив (список) элементов. Массив будет вставлен в элемент с id=root в виде
Элемента массива — это объекты, описывающие теги
За парсинг массива отвечает функция boxing. Если встретится элемент, содержащий массив, она рекурсивно вызовет себя.
Пуп земли, конечно, div.
В простейшем случае это строка: dict(div='Привет', className='h2')
Но может быть и массив (массив массивов):
Здесь 3 панели (каждая в отдельном файле: subFormTop.py и т.д.).
subFormTop.panel() возвращает массив для построения верхней панели.
subFormLeft.panel() и subFormRight.panel() объединены в строку ('className': 'row') и описывают левую и правую панели.
subFormDown.panel() закомментирована (не пригодилась).
Возможно, покажется сложным. Но это Питон: все можно упростить.
Пример формы, из журнала «Отчеты». Функция labField(метка, имя_поля_БД) возвращает массив из двух словарей (строку таблицы): 1-й словарь – это {'div': метка}, второй {'field': [имя_поля_БД, 'tx']}.
Примеры из sova/api/forms/home/top.py (стартовая на sova.online):
питоновский словарь
{'a': 'React v16', 'href': 'https://reactjs.org'}
Породит понятную React компоненту
Img поумнее стандартного – в пропсах можно задать href и target:
Питон:
dict(img='image?react.ico', style={'width':16}, href='https://reactjs.org')
Фрагмент парсера, преобразующего массив объектов в компоненты (boxing.js):
Наберите в поисковике «react component library». Результат предсказуем, — очень много. Но все это изобилие предназначено веб-сайтам, не приложениям:
Smart-textarea – пожалуй, единственный контрол, который меня устроил.
React-select –упростил и переделал выпадающий список
Data-picker/calendar – не нашел ничего подходящего. Написал свой, взяв за образец встроенный в G.Chrome.
Upload / Download – ничего подходящего, написал свой.
Imho: у веб-сайтов грустное будущее. Подавляющее большинство пользователей в ближайшем будущем перестанет пользоваться браузерами (или уже перестало). Телефон срастется с планшетом, а 1 тире 10 приложений полностью обеспечат потребности.
Я уже дважды сталкивался с программистами, которые не знают, как правильно написать адрес e-mail. Зачем им помнить то, чем они не пользуются. Мир меняется.
В Сове контроллеры не идеальные, но они разрабатывались в расчете на оператора, а не веб-пользователя.
Как пример форма «Отметка о передаче». Достаточно универсальная форма, используется там, где есть начальник. Красным на скриншоте обведены поля, управляющие скрытием. Дополнительные резолюции открываются автоматически по мере заполнения, если в резолютивной части несколько поручений разным группам исполнителям с разными сроками. Два срока на группу: 1-й срок 1-му исполнителю, 2-й срок соисполнителям.
Потрогать форму можно ЗДЕСЬ
Контроллер – это компонента React связанная с полем БД.
Подробное описание контроллеров с возможностью проверить их работу есть на Sova.online.
Обратите внимание на типы rtf и json. Rtf отображается как текст, но, если в тексте встретится конструкция {_ {объект} _}, Сова выполнит для этой конструкции json.parse и добавит результат в форму. Поле с типом json должно хранить описание массива элементов разметки: [{элем1}, {элем2}, …]. json.parse выполняется перед рендерингом.
Поля с такими типами позволяют хранить разметку в БД или в файлах. Полезно при формировании отчетов и написании документации.
Список контроллеров для всех типов полей (controllers.js):
Для работы приложения необходим механизм манипуляции контроллерами.
В сове все контроллеры документа хранятся в переменной документа
this.register
Использовать refs я не рискнул из-за слухов, что редаксофилы его отменят.
Контроллер может иметь следующие интерфейсы:
getValue(param)
setValue(value, param)
setFocus()
changeDropList()
Для того, чтобы обратиться к нужному полю, есть методы документа
getField(fieldName, param)
setField(fieldName, value, param)
changeDropList(fieldName, param)
setFocus(fieldName)
Для полей типа FileShow есть метод fileShow['FILES1_'].hasAtt(), где FILES1_ имя области с файлами. Возвращает true, если есть вложения. В отметке о передаче таких областей 2.
Контроллеры могут генерировать событие «recalc». Если для данного поля прописан обработчик, он выполнится. Обработчики находятся в подгружаемых js-файлах.
Пример и несколько упрощенное описание:
Есть форма «Отметка о передаче» (o.py). В ней прописан подгружаемый файл o.js
В o.js прописаны обработчики
, а также прописаны условия скрытия (project, op, prj1, prj2…prj5 – это свойство «name» в описании div'ов):
Как это работает: поле PROJECTO – это чекбокс, при изменении значения контроллер генерирует событие recalc, документ вызывает обработчик recalc.PROJECTO(this).
Обработчик просто вызывает forceUpdate() – перерисовать документ.
При перерисовке проверяется, есть ли у компоненты в пропсах name, есть ли для этого name функция hide[props.name] и не вернет ли она true.
prj3: doc => !doc.getField('projectO') || (!doc.getField('whoPrj2') && !doc.getField('whoPrj3'))
Скрывать третью резолюцию (область с props.name === 'prj3'), если чекбокс 'projectO' в состоянии OFF или в полях резолюций 2 и 3 исполнители не введены (оба поля 'whoPrj2' и 'whoPrj3' пустые).
Имя поля при вызове функций регистронезависимо.
WHOPRJ2 – это поле со списком, при выборе значения контроллер также сгенерит событие recalc, которое также вызовет перерисовку. Выбрав исполнителя во второй резолюции, вы тем самым откроете третью.
В подгружаемых js-файлах можно:
— управлять скрытием;
— управлять только чтением;
— реагировать на изменения полей;
— выполнять команды кнопок;
— делать валидацию полей и формы перед сохранением;
Подгружаемый файл для формы 'fo':
Валидация полей – функция, возвращает пусто, если все ОК, или сообщение о том, что не так. Сова установит фокус на невалидное поле.
Валидация формы – промис. В примере проверок нет (всегда вызывается yes), просто что-то выполняется перед отправкой на сервер.
В redux-form валидация сделана через trow – дикость какая-то.
Для тех, кто не знаком с промисами, пример простейшего:
Класс Document имеет несколько предопределенных команд, которые можно использовать в кнопках:
edit: перейти в режим редактирования формы
save: сохранить форму
close: закрыть форму
saveClose: сохранить и закрыть форму
prn: распечатать форму с выбором шаблона для печати
docOpen: открыть документ
dbOpen: открыть журнал
xopen: открыть url
newDoc: создать новый документ с нужной формой
В Redux-form api побогаче, — в Сове только необходимое.
Multi-page.
Класс Document создает объект (форму), который встраивается в элемент
Назовем его «корневой документ». Если в корневой документ добавить элемент
<div style={{position: 'absolute', zIndex: 999}}/>, в него точно также можно вставить другой объект Document.
Как быть с подгружаемыми обработчиками команд? Все просто: каждая форма имеет свой обработчик (свой js), и корневой документ должен загрузить те из них, которые могут потребоваться.
Пример для стартовой страницы sova.online (home.py)
Форма home.py, чтобы продемонстрировать многостраничность, открывает документы с формами «rkckg», «outlet», «outlet.gru», «o».
Чтобы все формы корректно работали, необходимо прописать в home.py скрипты для этих форм:
Поскольку при вызове любой функции обработчика первым параметром передается ссылка на документ, действия будут выполнены с нужным документом.
ООП и никаких чудес.
React – не React
Я уже описывал форму «report». Она открывается из менеджера отчетов (стрелка «React») и описывает параметры сбора отчета.
Сами отчеты (стрелка «не React») хранятся в подчиненных документах с формой «rreport» в виде html вложений. Мы занимались разработкой отчетов, когда React не было, форма «rreport» получилась простая (20 строк html и 15 строк просто-js), зачем менять то, что 8 лет работает.
Открыть менеджер отчетов.
Форма «rreport» состоит из 4-х кнопок и iframe. Сова перед открытием документа заменяет в айфрейме src="" на строку с url для скачивания html-вложения, остальное делает браузер.
С кнопками EXCEL / WORD аналогично: вставляем в нужное место кнопки url для скачивания с именем файла «report.html.xls» или «report.html.doc» и соответствующим mime-type. Остальное делают Excel/Word («эти умные животные отлично понимают все, что от них хотят» ).
Из do_get.py:
При открытии html в Excel/Word отличия от браузера есть, но небольшие. Да и статья не об этом.
Делаем форму с нуля.
Исходные данные:
Есть 3 функции
def snd(*msg, cat='snd'):
def err(*msg, cat='all'):
def dbg(*msg, cat='snd'):
, которые более-менее равномерно распределены по всему коду и пишут в лог-файл сообщения об ошибках и прочую хрень.
Формат сообщений передается в logging.Formatter в виде:
'%(asctime)s %(levelname)s [%(name)s] %(message)s'
Файл заполняется сообщениями
…
02.09.2018 17:50:07 DEBUG [http-server] addr ('127.0.0.1', 49964), «GET /arm HTTP/1.1» 200 –
02.09.2018 17:54:07 INFO [Free space] Вложения сохраняются в ".\DB\files" Свободно 68557 Mb
02.09.2018 17:58:07 ERROR [do_get.py] getScript: [Errno 2] No such file or directory: 'sova/api/forms/o/oo.js'
…
Дата-время, затем уровень, затем в квадратных скобках категория внутри уровня, затем сообщение.
Задача:
Сделать страницу для просмотра лог-файла. Что типа
Назовем форму «lm», формироваться она будет функцией page в модуле api/forms/lm.py
В левой части 2 поля, оба с типом «list»: type и cat (тип сообщения и категория).
В правой одно поле msg с типом fd (forDisplayOnly).
Типы сообщений прописаны в описании поля (['Весь журнал|all', 'Ошибки|err',...),
категории вытягиваются по xhr из глобального словаря с вызовом хитрого url:
api.get?loadDropList&logger|keys_err вернет в json-формате массив (список) категорий из глобального словаря. Что-то типа well('logger', 'keys_err').
Сообщения формируются при открытии документа функцией queryOpen в lm.py
logParser считывает и парсит log-файл. Результаты раскладывает в несколько массивов и сохраняет их в глобальном словаре. Ничего интересного: 2 простейших re и цикл по итератору.
Функции для работы с глобальным словарем:
toWell(o, key1, [key2]) — сохранить в глобальном словаре объект «o»
well(key1, [key2]) — взять из глобального словаря объект по ключу (по двум ключам).
Для первой прорисовки этого достаточно. Чтобы можно было отобразить сообщения нужного типа и нужной категории, необходимо сделать подгружаемый js.
В lm.py добавляем строку
javaScriptUrl = 'jsv?api/forms/lm/lm.js'
и создаем lm.js:
getLogData вытягивает с сервера сообщения нужного типа и нужной категории:
Насладиться формой можно ЗДЕСЬ.
Изначально логгирование было сделано на основе стандартного модуля logging
с использованием logging.FileHandler, .addHandler и прочих getLogger и setFormatter.
Как учили. Но при этом глючило. Можете кидаться камнями, но когда я выкинул logging и стал просто писать в файл, код стал короче, понятней и глюки исчезли.
В комплекте есть самописный многопоточный wsgi сервер с Digest авторизацией. Это не для сайтов. Зачем он вообще понадобился?
У заказчика 40 юр. лиц, в большинстве случаев с системой работают 1-2-3 человека. Хранить данные в интернете запрещено. У всех win 7. Требуется простота установки и конфигурации.
Решение: с помощью cx-Freeze и Inno Setup делаем инсталлятор, запускаем его на компьютере самого ответственного и получаем мини-http сервер для локальной сети, стартующий как сервис Windows. Ничего лишнего. Использовать встроенный в Питон wsgiref.simple_server или wsgi_Werkzeug нельзя, т.к. они однопоточные: пока один запрос не отработает, другие будут ждать.
Вряд ли я кого удивлю, сообщив, что встроенный в Django WSGIServer/0.2 CPython/3.5.3 в разы быстрее самописного питоновского. Только это не имеет значения, — формы и справочники кэшируются на клиенте, по локальной сети очень быстро передаются только данные БД.
Есть еще одна причина: десктопное приложение имеет доступ к ресурсам компьютера (ЭЦП, файлы, сканер...). Чтобы из браузера получить аналогичный доступ надо или писать плагин, или повесить в сервисах маленький http-сервер, который может и с главным сервером снюхаться, и нужные действия на локале выполнит.
Сова не использует инструменты фреймворков для работы с БД. В директории dbToolkit нечто похожее по структуре на MongoDB (или на Lotus Notes) на SQLite3:
Класс Book – db (в терминологии MongoDB и Lotus Notes)
Класс DocumentCollection – коллекция документов из Book
Класс Document – документ (объект, содержащий любое количество полей).
Установка:
Скачать с sova.online архив owl.zip
В архиве каталог owl, из которого можно запустить Сову из django, falcon или без фреймворков.
Скачать, распаковать.
Установить Python3 (3.5+)
1. owl – без фреймворков. Внимание! Логин: 1, пароль: 1
Linux:
cd ./owl
python3 wsgi_sova.py
или в отдельном окне
screen -Udm python3 wsgi_server.py
Windows:
cd ./owl
wsgi_sova.py
2. Django
Linux:
Установить django:
pip3 install django
cd ./owl
python3 manage.py runserver
или в отдельном окне
screen -Udm python3 manage.py runserver 127.0.0.1:8000
Windows:
Установить django:
pip install django
cd ./owl
manage.py runserver
3. falcon
Linux:
pip3 install falcon
cd ./owl
python3 wsgi_sova.py falconApp:api 8001 log_falcon/falcon
Windows:
pip install falcon
cd ./owl
wsgi_sova.py falconApp:api 8001 log_falcon/falcon
*********************
— название у статьи странное, сам то понял, что такое «Multipage SPA»?
— обычный маркетинговый ход
— а почему без Redux? Все используют Redux
— слово «редюсер» мне не нравится
— а серьезно? СombineReducers на любом уровне иерархии… Это так красиво
— это multipage, детка. Обработчики команд должны быть внутри формы, а не как рога у оленя
— зачем ты вообще статью написал?
— пиарюсь
Сова – это нано-фреймворк, который можно встроить в другие фреймворки.
Картинка с sova.online, на котором запущено 3 http сервера:
http://sova.online/ — просто Falcon
http://sova.online:8000/ — просто Django
http://sova.online:8001/ — просто Python (логин: 1, пароль: 1)
Там же исходные коды и инструкция по установке. Там же нет рекламы.
Идея делать сайты на Питоне с прорисовкой на React не нова. Есть замечательный фреймворк https://plot.ly/products/dash/, зачем еще что-то делать?
Объясняю: Сова не рассчитана на разработку сайтов. Это инструмент для замены толстых клиентов на приложения, работающие через браузер (десктопные приложения).
— Что-ли веб-клиент?
— Нет. Это не веб-клиент. Это приложение, работающее в браузере.
— Не понимаю.
— К сожалению, и многие разработчики не понимают.
Я как генеральный активно работал с несколькими интернет приложениями.
Онлайн клиент банка Югра (банк закрыт).
Хорошее было приложение, но это был Java applet, т.е. толстый клиент, запускаемый из браузера. И банк Югра, и апплеты в прошлом.
Онлайн клиент банка ВТБ-24 (банк закрыт).
Я гуманист, но поработав с этим чудом, стали появляться жестокие мысли типа: «Заставить разработчика зарегистрировать в нем 1000 платежек».
При этом как веб-клиент он прекрасен. Анимация, открывается на мобильнике. Вау! Круто!
Спросил знакомого бухгалтера: как ты с ним работаешь?
Она говорит: прекрасно! загружаю данные в 1с, в 1с работаю, результаты выгружаю обратно.
Онлайн клиент сбербанка
Удовлетворительный клиент, работать можно. Когда меня попросили его оценить, я поставил ему 3 бала из 5 и дал список замечаний. Это при моих 10 платежках в месяц. Те, кто делает 100 платежек в день, скорее всего, выгружают информацию.
Заполнение платежки.
Зеленая область, занимающая 20% экрана – это меню. Оно не просто мешает (position: fixed), оно говорит о том, что разработчик не профессионал. Если я начал создавать платеж, на экране д.б. 3 кнопки: «Создать», «Сохранить как шаблон», «Отмена». Эти кнопки есть (почему-то внизу). Здесь не multi-page SPA: перейдешь по пункту меню, — данные в форме пропадут.
Тот, кто это делал, даже не поймет, в чем наезд: «Нормально сделано, все так делают, такая библиотека, люди ведь работают …». И он прав. Спрашивать надо с руководителя проекта, а для руководителя главное – это БД и слои в концептуальной модели. А формы – наймем мальчиков, они нарисуют. И ведь гордятся, наверное, этой халтурой.
Торговые площадки (5 штук, 44 ФЗ)
Это действительно приложения (не веб-клиенты). Но контроллеры полей мне не нравятся.
Примеры:
Выровнено странно, ширина поля явно недостаточна, autoheight в поле ввода отсутствует.
Еще пример. В поле «Дата публикации» нет шаблона дд.мм.гггг, календарь с ошибкой, пиктограмма календаря пугает:
Список на rts-tender: есть выделенная цветом текущая запись, стрелками можно двигаться по списку, но нет автоскроллинга (можно убежать за границу экрана), ни Enter, ни пробел не открывают ссылку, табулятор не привязан к текущей записи. Хотя открыть ссылку можно только мышкой, я оцениваю контрол со знаком плюс. Такого функционала (запомнить и подсветить текущий документ) мне не хватает в mail.ru
Вроде как мелочи. Но профессиональное приложение отличается от полупрофессионального именно мелочами. Конечному пользователю наплевать, какая у вас БД и сколько слоев в концептуальной модели. Он работает с экранными формами и у него 3 требования: функционально, удобно, быстро.
К сожалению, выбор системы определяют айтишники и начальники, которые сами с системой не работали и работать не будут. Скорость они оценят, функциональность оценят так, как они это понимают, а на удобство им наплевать, главное, чтобы было красиво.
Павел Валерьевич Дуров не изобретал ни соцсеть, ни мессенджер. Он сделал то, что нужно пользователям, удобно и красиво. И люди это оценили, в том числе и материально.
Сова – это инструмент для построения профессионального интерфейса.
Что это значит, на примере СЭД.
Есть СЭД, в ней 3 группы пользователей:
Начальство
Специалисты, готовящие документы
Делопроизводители.
Начальники, они как дети. Им нужно, чтобы просто и красиво. В идеале 1 кнопка и 1 поле. И чтобы понтов побольше. Веб-клиент и, конечно, мобильный клиент, чтобы понты демонстрировать.
Специалисты. Веб-клиент, помесь соцсеть/почтовик. Не забывайте, что специалистов много и их надо обучать. Чем привычней будет для них среда, тем лучше. Мобильный клиент тоже пригодится, если служба безопасности разрешит.
Делопроизводители. Вот здесь пригодится Сова. Делопроизводители – это системообразующая группа пользователей. Все остальные могут заболеть/уйти в отпуск/перестать пользоваться, — СЭД будет работать. Если остановится регистрация, встанет все.
Делопроизводство – это конвейер, и здесь важно все, любая мелочь: шрифты, полутона, автоматическое заполнение, проверка значений, удобство ввода и т.д.
СЭД «Дело». Кабинеты исполнителей сделаны на веб-клиенте, канцелярия – толстый клиент. Все прекрасно, но это будет работать, пока правительство не запретит Windows в госструктурах. Я люблю Win 7, но если бы правителем был я, it-рынок взбодрился новыми заказами, а MS остался в светлой памяти. Кстати, 6 декабря Антон Силуанов подписал директиву о переходе на отечественное ПО.
Sova.online
Как Сова открывает форму.
Без multi-page.
Центральным элементом Совы является компонент Document. Нажав ctrl-U на стартовой странице, вы увидите все, что нужно для создания объекта класса Document:
— данные полей БД;
— url формы для отображения;
— dbAlias, unid — для работы с БД;
— что-то там еще.
В некоторой степени Document – это аналог Redux-form.
Форма загружается в виде JSON строки, затем бывший словарь становится объектом, имеющим style, className и массив (список) элементов. Массив будет вставлен в элемент с id=root в виде
<div style className>…массив…</div>
Элемента массива — это объекты, описывающие теги
<div>, <a>, <img>, <button>
, или массив, или компоненты.За парсинг массива отвечает функция boxing. Если встретится элемент, содержащий массив, она рекурсивно вызовет себя.
Пуп земли, конечно, div.
В простейшем случае это строка: dict(div='Привет', className='h2')
Но может быть и массив (массив массивов):
def style(**par):
return {'style': {**par}}
dict( # словарь описывает стартовую страницу sova.online
style(position='relative'),
readOnly = 1,
div = [
dict( style(width=1000, margin='auto', paddingTop=20),
div=[
{ 'div': subFormTop.panel() },
{ 'div': [subFormLeft.panel(), subFormRight.panel()], 'className': 'row' },
# { 'div': [subFormDown.panel()] },
]),
]
)
Здесь 3 панели (каждая в отдельном файле: subFormTop.py и т.д.).
subFormTop.panel() возвращает массив для построения верхней панели.
subFormLeft.panel() и subFormRight.panel() объединены в строку ('className': 'row') и описывают левую и правую панели.
subFormDown.panel() закомментирована (не пригодилась).
Возможно, покажется сложным. Но это Питон: все можно упростить.
Пример формы, из журнала «Отчеты». Функция labField(метка, имя_поля_БД) возвращает массив из двух словарей (строку таблицы): 1-й словарь – это {'div': метка}, второй {'field': [имя_поля_БД, 'tx']}.
div = [
docTitle('Отчет'),
dict ( wl='40mm', className='cellbg-green', div=_table(
labField('Отчет', 'nodafd'),
labField('Запуск отчета', '_STARTINGTIME'),
labField('Окончание', '_ENDTIME'),
labField('Пользователь', 'CREATOR'),
labField('Категория', 'REPORTCAT'),
labField('Название', 'REPORTNAME'),
labField('Заголовок', 'REPORTTITLE'),
labField('Начало периода', 'dt1'),
labField('Конец периода', 'dt2'),
labField('Начало периода 2', 'dt3'),
labField('Конец периода 2', 'dt4'),
labField('Журналы', 'LBYEARS'),
labField('Префиксы', 'GRGROUP'),
labField('Формула отбора', 'QUERYMAIN'),
labField('Комментарий', 'NOTES'),
)),
sent(),
]
Примеры из sova/api/forms/home/top.py (стартовая на sova.online):
питоновский словарь
{'a': 'React v16', 'href': 'https://reactjs.org'}
Породит понятную React компоненту
<a href={'https://reactjs.org'}>React v16</a>
Img поумнее стандартного – в пропсах можно задать href и target:
Питон:
dict(img='image?react.ico', style={'width':16}, href='https://reactjs.org')
Фрагмент парсера, преобразующего массив объектов в компоненты (boxing.js):
if ( td.img ) { // td – это элемент массива
let img = <img src={td.img} прочие_пропсы/>;
return td.href ?
<a href={td.href} key={i} target={td.target}>{img}</a>
:
img;
}
Наберите в поисковике «react component library». Результат предсказуем, — очень много. Но все это изобилие предназначено веб-сайтам, не приложениям:
Smart-textarea – пожалуй, единственный контрол, который меня устроил.
React-select –упростил и переделал выпадающий список
Data-picker/calendar – не нашел ничего подходящего. Написал свой, взяв за образец встроенный в G.Chrome.
Upload / Download – ничего подходящего, написал свой.
Imho: у веб-сайтов грустное будущее. Подавляющее большинство пользователей в ближайшем будущем перестанет пользоваться браузерами (или уже перестало). Телефон срастется с планшетом, а 1 тире 10 приложений полностью обеспечат потребности.
Я уже дважды сталкивался с программистами, которые не знают, как правильно написать адрес e-mail. Зачем им помнить то, чем они не пользуются. Мир меняется.
В Сове контроллеры не идеальные, но они разрабатывались в расчете на оператора, а не веб-пользователя.
Как пример форма «Отметка о передаче». Достаточно универсальная форма, используется там, где есть начальник. Красным на скриншоте обведены поля, управляющие скрытием. Дополнительные резолюции открываются автоматически по мере заполнения, если в резолютивной части несколько поручений разным группам исполнителям с разными сроками. Два срока на группу: 1-й срок 1-му исполнителю, 2-й срок соисполнителям.
Потрогать форму можно ЗДЕСЬ
Контроллер – это компонента React связанная с полем БД.
Подробное описание контроллеров с возможностью проверить их работу есть на Sova.online.
Обратите внимание на типы rtf и json. Rtf отображается как текст, но, если в тексте встретится конструкция {_ {объект} _}, Сова выполнит для этой конструкции json.parse и добавит результат в форму. Поле с типом json должно хранить описание массива элементов разметки: [{элем1}, {элем2}, …]. json.parse выполняется перед рендерингом.
Поля с такими типами позволяют хранить разметку в БД или в файлах. Полезно при формировании отчетов и написании документации.
Список контроллеров для всех типов полей (controllers.js):
export const controller = prop => {
switch (prop.type) { //тип поля
case 'chb': return <Checkbox {...prop}/>;
case 'lbse': // listbox single enable (разрешить значения не из списка)
case 'lbme': // listbox multivalue enable
// lbse/lbme - это текстовое поле с кнопкой, открывающей список
case 'tx': return <Text {...prop}/>; // smart-textarea
case 'lbsd': // listbox single disables (запретить значения не из списка)
case 'lbmd': return <ListBox {...prop}/>;
case 'dt': return (prop.readOnly ?
<Text {...prop} xValue={Util.dtRus(prop.xValue)} />
:
<Datepicker {...prop}/>);
case 'fd': return <ForDisplayOnly {...prop}/>;
case 'table':
case 'gr': return <Table {...prop}/>;
case 'rtf': return <RTF {...prop}/>;
case 'json': return <JsonArea {...prop}/>;
case 'list': return <List {...prop}/>;
case 'view': return <View {...prop}/>;
default:
console.warn('Неизвестный тип поля', prop.xName, prop.type);
return <Text {...prop}/>;
};
};
Для работы приложения необходим механизм манипуляции контроллерами.
В сове все контроллеры документа хранятся в переменной документа
this.register
Использовать refs я не рискнул из-за слухов, что редаксофилы его отменят.
Контроллер может иметь следующие интерфейсы:
getValue(param)
setValue(value, param)
setFocus()
changeDropList()
Для того, чтобы обратиться к нужному полю, есть методы документа
getField(fieldName, param)
setField(fieldName, value, param)
changeDropList(fieldName, param)
setFocus(fieldName)
Для полей типа FileShow есть метод fileShow['FILES1_'].hasAtt(), где FILES1_ имя области с файлами. Возвращает true, если есть вложения. В отметке о передаче таких областей 2.
Контроллеры могут генерировать событие «recalc». Если для данного поля прописан обработчик, он выполнится. Обработчики находятся в подгружаемых js-файлах.
Пример и несколько упрощенное описание:
Есть форма «Отметка о передаче» (o.py). В ней прописан подгружаемый файл o.js
В o.js прописаны обработчики
recalc: {
PROJECTO: doc => doc.forceUpdate(),
WHOPRJ2: doc => doc.forceUpdate(),
WHOPRJ3: doc => doc.forceUpdate(),
… другие обработчики
}
, а также прописаны условия скрытия (project, op, prj1, prj2…prj5 – это свойство «name» в описании div'ов):
hide: {
project: doc => !doc.getField('projectO'), // скрыть, если поле PROJECTO пустое
op: doc => doc.getField('projectO'), // скрыть, если поле PROJECTO не пустое
prj1: doc => !doc.getField('projectO'),
prj2: doc => !doc.getField('projectO'),
prj3: doc => !doc.getField('projectO') || (!doc.getField('whoPrj2') && !doc.getField('whoPrj3')),
prj4: doc => !doc.getField('projectO') || (!doc.getField('whoPrj3') && !doc.getField('whoPrj4')),
prj5: doc => !doc.getField('projectO') || (!doc.getField('whoPrj4') && !doc.getField('whoPrj5')),
},
Как это работает: поле PROJECTO – это чекбокс, при изменении значения контроллер генерирует событие recalc, документ вызывает обработчик recalc.PROJECTO(this).
Обработчик просто вызывает forceUpdate() – перерисовать документ.
При перерисовке проверяется, есть ли у компоненты в пропсах name, есть ли для этого name функция hide[props.name] и не вернет ли она true.
prj3: doc => !doc.getField('projectO') || (!doc.getField('whoPrj2') && !doc.getField('whoPrj3'))
Скрывать третью резолюцию (область с props.name === 'prj3'), если чекбокс 'projectO' в состоянии OFF или в полях резолюций 2 и 3 исполнители не введены (оба поля 'whoPrj2' и 'whoPrj3' пустые).
Имя поля при вызове функций регистронезависимо.
WHOPRJ2 – это поле со списком, при выборе значения контроллер также сгенерит событие recalc, которое также вызовет перерисовку. Выбрав исполнителя во второй резолюции, вы тем самым откроете третью.
В подгружаемых js-файлах можно:
— управлять скрытием;
— управлять только чтением;
— реагировать на изменения полей;
— выполнять команды кнопок;
— делать валидацию полей и формы перед сохранением;
Подгружаемый файл для формы 'fo':
window.sovaActions = window.sovaActions || {};
window.sovaActions.fo = { // fo – имя формы на нижнем регистре
recalc: { // обработчик вызывается при изменении значений чекбоксов и полей со списком
PROJECTO: doc => doc.forceUpdate(),
},
hide: { // скрывать именованную область, если true
project: doc => !doc.getField('projectO'),
},
readOnly: { // только чтение для именованной области, если true
who: doc => doc.getField('SENTFROMDB'),
},
validate: { // проверка полей и формы перед отправкой на сервер
who: doc => doc.getField('who') ? '' : 'Не заполнено поле "Кому направлено"',
form: doc => new Promise( (yes, no) => {
let disableAutoOrder = false;
for (let i = 1; i <= 5; i++) {
let val = doc.getField('RESPRJ' + i);
disableAutoOrder |= /за моей подписью/.test(val);
}
disableAutoOrder && doc.setField('AUTOORDER', '');
yes();
}),
},
cmd: { // выполнение команд кнопок
logoff: doc => { window.location.href = '/logoff' },
},
}
Валидация полей – функция, возвращает пусто, если все ОК, или сообщение о том, что не так. Сова установит фокус на невалидное поле.
Валидация формы – промис. В примере проверок нет (всегда вызывается yes), просто что-то выполняется перед отправкой на сервер.
В redux-form валидация сделана через trow – дикость какая-то.
Для тех, кто не знаком с промисами, пример простейшего:
const confirmDlg = msg => new Promise((ok, cancel) => confirm(msg) ? ok('Нажата кнопка ОК') : cancel('Нажата кнопка cancel'));
confirmDlg('Самый простой промис')
.then( s => console.log(s))
.catch( s => console.log(s));
Класс Document имеет несколько предопределенных команд, которые можно использовать в кнопках:
edit: перейти в режим редактирования формы
save: сохранить форму
close: закрыть форму
saveClose: сохранить и закрыть форму
prn: распечатать форму с выбором шаблона для печати
docOpen: открыть документ
dbOpen: открыть журнал
xopen: открыть url
newDoc: создать новый документ с нужной формой
В Redux-form api побогаче, — в Сове только необходимое.
Multi-page.
Класс Document создает объект (форму), который встраивается в элемент
<div id="root"></div>
.Назовем его «корневой документ». Если в корневой документ добавить элемент
<div style={{position: 'absolute', zIndex: 999}}/>, в него точно также можно вставить другой объект Document.
Как быть с подгружаемыми обработчиками команд? Все просто: каждая форма имеет свой обработчик (свой js), и корневой документ должен загрузить те из них, которые могут потребоваться.
Пример для стартовой страницы sova.online (home.py)
Форма home.py, чтобы продемонстрировать многостраничность, открывает документы с формами «rkckg», «outlet», «outlet.gru», «o».
Чтобы все формы корректно работали, необходимо прописать в home.py скрипты для этих форм:
javaScriptUrl = ['jsv?api/forms/rkckg/rkckg.js',
'jsv?api/forms/outlet_gru/outlet_gru.js',
'jsv?api/forms/outlet/outlet.js',
'jsv?api/forms/o/o.js',
'jsv?api/forms/home/home.js']
Поскольку при вызове любой функции обработчика первым параметром передается ссылка на документ, действия будут выполнены с нужным документом.
ООП и никаких чудес.
React – не React
Я уже описывал форму «report». Она открывается из менеджера отчетов (стрелка «React») и описывает параметры сбора отчета.
Сами отчеты (стрелка «не React») хранятся в подчиненных документах с формой «rreport» в виде html вложений. Мы занимались разработкой отчетов, когда React не было, форма «rreport» получилась простая (20 строк html и 15 строк просто-js), зачем менять то, что 8 лет работает.
Открыть менеджер отчетов.
Форма «rreport» состоит из 4-х кнопок и iframe. Сова перед открытием документа заменяет в айфрейме src="" на строку с url для скачивания html-вложения, остальное делает браузер.
С кнопками EXCEL / WORD аналогично: вставляем в нужное место кнопки url для скачивания с именем файла «report.html.xls» или «report.html.doc» и соответствующим mime-type. Остальное делают Excel/Word («эти умные животные отлично понимают все, что от них хотят» ).
Из do_get.py:
downloadUrl = '/download/' + fn + '?' + '&'.join([d.db.alias, d.unid, idbl, fsName, fzip, ctype, flen])
excel = '/download/%s.xls?%s' % (fn, '&'.join([d.db.alias, d.unid, idbl, fsName, fzip, 'application/x-excel', flen]))
word = '/download/%s.doc?%s' % (fn, '&'.join([d.db.alias, d.unid, idbl, fsName, fzip, 'application/msword', flen]))
html = html.replace('src=""', 'src="%s"' % downloadUrl).replace('openExcel', excel).replace('openWord', word)
При открытии html в Excel/Word отличия от браузера есть, но небольшие. Да и статья не об этом.
Делаем форму с нуля.
Исходные данные:
Есть 3 функции
def snd(*msg, cat='snd'):
def err(*msg, cat='all'):
def dbg(*msg, cat='snd'):
, которые более-менее равномерно распределены по всему коду и пишут в лог-файл сообщения об ошибках и прочую хрень.
Формат сообщений передается в logging.Formatter в виде:
'%(asctime)s %(levelname)s [%(name)s] %(message)s'
Файл заполняется сообщениями
…
02.09.2018 17:50:07 DEBUG [http-server] addr ('127.0.0.1', 49964), «GET /arm HTTP/1.1» 200 –
02.09.2018 17:54:07 INFO [Free space] Вложения сохраняются в ".\DB\files" Свободно 68557 Mb
02.09.2018 17:58:07 ERROR [do_get.py] getScript: [Errno 2] No such file or directory: 'sova/api/forms/o/oo.js'
…
Дата-время, затем уровень, затем в квадратных скобках категория внутри уровня, затем сообщение.
Задача:
Сделать страницу для просмотра лог-файла. Что типа
Назовем форму «lm», формироваться она будет функцией page в модуле api/forms/lm.py
def page(dbAlias, mode, userName, multiPage):
return dict(
style(background='url(/image?bg51.jpg)', backgroundSize='100% 100%'),
div=[
dict(
style(width='200px', float='left', background='rgba(210,218,203, 0.5)', padding='0 5px'),
div=[
_field('type', 'list', ['Весь журнал|all', 'Ошибки|err', 'Сообщения|info', 'Отладка|debug'],
saveAlias=1,
**style(margin='10px auto', width=170, height=110)
),
_field('cat', 'list', 'TYPE_ALIAS|||api.get?loadDropList&logger|keys_{FIELD}',
listItemClassName='repName',
listItemSelClassName='repNameSel',
**style(height='calc(100vh - 133px)', overflow='auto')
)
],
),
_field('msg', 'fd',
br=1,
**style(overflow='auto', height='100vh', font='bold 12px Courier', background='rgba(255,255,255, 0.8)')
),
]
)
В левой части 2 поля, оба с типом «list»: type и cat (тип сообщения и категория).
В правой одно поле msg с типом fd (forDisplayOnly).
Типы сообщений прописаны в описании поля (['Весь журнал|all', 'Ошибки|err',...),
категории вытягиваются по xhr из глобального словаря с вызовом хитрого url:
api.get?loadDropList&logger|keys_err вернет в json-формате массив (список) категорий из глобального словаря. Что-то типа well('logger', 'keys_err').
Сообщения формируются при открытии документа функцией queryOpen в lm.py
def queryOpen(d, mode, ground):
logParser()
ls = well('logger_all', 'A L L')
s = '\n'.join(reversed(ls))
d.msg = s
d.type_alias = 'all'
logParser считывает и парсит log-файл. Результаты раскладывает в несколько массивов и сохраняет их в глобальном словаре. Ничего интересного: 2 простейших re и цикл по итератору.
Функции для работы с глобальным словарем:
toWell(o, key1, [key2]) — сохранить в глобальном словаре объект «o»
well(key1, [key2]) — взять из глобального словаря объект по ключу (по двум ключам).
Для первой прорисовки этого достаточно. Чтобы можно было отобразить сообщения нужного типа и нужной категории, необходимо сделать подгружаемый js.
В lm.py добавляем строку
javaScriptUrl = 'jsv?api/forms/lm/lm.js'
и создаем lm.js:
window.sovaActions = window.sovaActions || {};
window.sovaActions.lm = { // обработчики формы "lm"
init: doc => doc.changeDropList('CAT'),
recalc: {
TYPE: (doc, label, alias) => {
doc.changeDropList('CAT');
getLogData(doc, alias + '|A L L');
},
CAT: (doc, label) => getLogData(doc, doc.getField('type_alias') + '|' + label),
},
};
// *** *** ***
let getLogData = (doc, keys) => {
fetch('api.get?getLogData&' + keys, {method: 'get', credentials: 'include'})
.then( response => response.text() )
.then( txt => doc.setField('msg', txt) )
.catch( err => doc.setField('msg', err.message) );
};
getLogData вытягивает с сервера сообщения нужного типа и нужной категории:
def getLogData(par, un):
lg, _, cat = par.partition('|')
msg = well('logger_' + lg, cat)
return 200, 'text/html; charset=UTF-8', '\n'.join(reversed(msg))
Насладиться формой можно ЗДЕСЬ.
Изначально логгирование было сделано на основе стандартного модуля logging
с использованием logging.FileHandler, .addHandler и прочих getLogger и setFormatter.
Как учили. Но при этом глючило. Можете кидаться камнями, но когда я выкинул logging и стал просто писать в файл, код стал короче, понятней и глюки исчезли.
В комплекте есть самописный многопоточный wsgi сервер с Digest авторизацией. Это не для сайтов. Зачем он вообще понадобился?
У заказчика 40 юр. лиц, в большинстве случаев с системой работают 1-2-3 человека. Хранить данные в интернете запрещено. У всех win 7. Требуется простота установки и конфигурации.
Решение: с помощью cx-Freeze и Inno Setup делаем инсталлятор, запускаем его на компьютере самого ответственного и получаем мини-http сервер для локальной сети, стартующий как сервис Windows. Ничего лишнего. Использовать встроенный в Питон wsgiref.simple_server или wsgi_Werkzeug нельзя, т.к. они однопоточные: пока один запрос не отработает, другие будут ждать.
Вряд ли я кого удивлю, сообщив, что встроенный в Django WSGIServer/0.2 CPython/3.5.3 в разы быстрее самописного питоновского. Только это не имеет значения, — формы и справочники кэшируются на клиенте, по локальной сети очень быстро передаются только данные БД.
Есть еще одна причина: десктопное приложение имеет доступ к ресурсам компьютера (ЭЦП, файлы, сканер...). Чтобы из браузера получить аналогичный доступ надо или писать плагин, или повесить в сервисах маленький http-сервер, который может и с главным сервером снюхаться, и нужные действия на локале выполнит.
Сова не использует инструменты фреймворков для работы с БД. В директории dbToolkit нечто похожее по структуре на MongoDB (или на Lotus Notes) на SQLite3:
Класс Book – db (в терминологии MongoDB и Lotus Notes)
Класс DocumentCollection – коллекция документов из Book
Класс Document – документ (объект, содержащий любое количество полей).
Установка:
Скачать с sova.online архив owl.zip
В архиве каталог owl, из которого можно запустить Сову из django, falcon или без фреймворков.
Скачать, распаковать.
Установить Python3 (3.5+)
1. owl – без фреймворков. Внимание! Логин: 1, пароль: 1
Linux:
cd ./owl
python3 wsgi_sova.py
или в отдельном окне
screen -Udm python3 wsgi_server.py
Windows:
cd ./owl
wsgi_sova.py
2. Django
Linux:
Установить django:
pip3 install django
cd ./owl
python3 manage.py runserver
или в отдельном окне
screen -Udm python3 manage.py runserver 127.0.0.1:8000
Windows:
Установить django:
pip install django
cd ./owl
manage.py runserver
3. falcon
Linux:
pip3 install falcon
cd ./owl
python3 wsgi_sova.py falconApp:api 8001 log_falcon/falcon
Windows:
pip install falcon
cd ./owl
wsgi_sova.py falconApp:api 8001 log_falcon/falcon
*********************
— название у статьи странное, сам то понял, что такое «Multipage SPA»?
— обычный маркетинговый ход
— а почему без Redux? Все используют Redux
— слово «редюсер» мне не нравится
— а серьезно? СombineReducers на любом уровне иерархии… Это так красиво
— это multipage, детка. Обработчики команд должны быть внутри формы, а не как рога у оленя
— зачем ты вообще статью написал?
— пиарюсь