Страница для индексации

Перейти на сайт

Семантическая раскраска текстов в html и React

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

Новый стек технологий: движок на ReactJS отрисует разметку, созданную на Python.

Альтернативный веб-клиент Lotus Notes (дешевле и эффективнее).

Sova-online - свободно распространяемый фреймворк с открытым кодом.

Серверная часть (Python 3.7+) - это аналог Dash, ориентированный на работу с формами в многооконном режиме.

Клиентская часть (React JS) - аналог Redux-form, обеспечивающий прорисовку форм, поступающих с сервера в JSON-формате, выполнение команд, валидацию полей и формы перед отправкой данных на сервер.

Сова открыта. Обработчики команд (пересчет полей, скрытия, запросы к серверу) могут быть в отдельных JS-файлах, подгружаемых при открытии конкретной формы. Добавление новых контроллеров несложно. Достаточно прописать новую компоненту в controllers.js. Если контроллер хранит в себе информацию, необходимо реализовать интерфейсы "getValue" и "setValue". Валидация полей и всей формы в подгружаемых JS.

Сова компактна. Javascript занимает около 250 Kb, Python - около 2000 строк, включая WSGI-сервер.

Сова надежна. Приложение, разработанное с использованием Совы, эксплуатируется с середины 2017. Ежедневно в режиме редактирования на 3 серверах работает около 200 пользователей.

Статья "Два уровня MVC при проектировании прогрессивных веб-приложений (PWA)"

1. Введение
серверная часть
клиентская часть
"Сова" для сайтов
2. Python
3. React
Дата (dt)
вложения
Список (list)
Вид (view)
Флажок (chb)
Текст (tx)
Только просмотр (fd, for display only)
класс "Document"
Поля со списком (lb..)
showCode
интерфейсы контроллеров
формирование страниц
структура формы
выполнение команд
Multi-page SPA
кнопка (button)
скрытие элементов
Поле с разметкой (rtf)
Таблица (table)
команды сервера
поля формы
Поле-разметка (json)
<b>Мост между Python и React</b>

Сова – это нано-фреймворк, который можно встроить в другие фреймворки.
Картинка с sova.online, на котором запущено 3 http сервера:
<a href="http://sova.online/">http://sova.online/</a> - просто Falcon
<a href="http://sova.online:8000/">http://sova.online:8000/</a> - просто Django
<a href="http://sova.online:8001/">http://sova.online:8001/</a> - просто Python (логин: 1, пароль: 1)
Там же исходные коды и инструкция по установке. Там же нет рекламы.

<img src="https://habrastorage.org/webt/gx/yn/h9/gxynh9nwwhuq9ik8ius_k7lvjgo.jpeg" />
<cut/>
Идея делать сайты на Питоне с прорисовкой на React не нова. Есть замечательный фреймворк <a href="https://plot.ly/products/dash/">https://plot.ly/products/dash/</a>, зачем еще что-то делать?
Объясняю: Сова не рассчитана на разработку сайтов. Это инструмент для замены толстых клиентов на приложения, работающие через браузер (десктопные приложения).

- Что-ли веб-клиент?
- Нет. Это не веб-клиент. Это приложение, работающее в браузере.
- Не понимаю.
- К сожалению, и многие разработчики не понимают.


Я как генеральный активно работал с несколькими интернет приложениями.

<b>Онлайн клиент банка Югра (банк закрыт).</b>
Хорошее было приложение, но это был Java applet, т.е. толстый клиент, запускаемый из браузера. И банк Югра, и апплеты в прошлом.

<b>Онлайн клиент банка ВТБ-24 (банк закрыт).</b>
Я гуманист, но поработав с этим чудом, стали появляться жестокие мысли типа: «Заставить разработчика зарегистрировать в нем 1000 платежек».
При этом как веб-клиент он прекрасен. Анимация, открывается на мобильнике. Вау! Круто!
Спросил знакомого бухгалтера: как ты с ним работаешь?
Она говорит: прекрасно! загружаю данные в 1с, в 1с работаю, результаты выгружаю обратно.

<b>Онлайн клиент сбербанка</b>
Удовлетворительный клиент, работать можно. Когда меня попросили его оценить, я поставил ему 3 бала из 5 и дал список замечаний. Это при моих 10 платежках в месяц. Те, кто делает 100 платежек в день, скорее всего, выгружают информацию.

Заполнение платежки.
<img src="https://habrastorage.org/webt/kx/0a/h2/kx0ah27hdkvgc6ob0-16ifdx5oq.jpeg" />
Зеленая область, занимающая 20% экрана – это меню. Оно не просто мешает (position: fixed), оно говорит о том, что разработчик не профессионал. Если я начал создавать платеж, на экране д.б. 3 кнопки: «Создать», «Сохранить как шаблон», «Отмена». Эти кнопки есть (почему-то внизу). Здесь не multi-page SPA: перейдешь по пункту меню, - данные в форме пропадут.
Тот, кто это делал, даже не поймет, в чем наезд: «Нормально сделано, все так делают, такая библиотека, люди ведь работают …». И он прав. Спрашивать надо с руководителя проекта, а для руководителя главное – это БД и слои в концептуальной модели. А формы – наймем мальчиков, они нарисуют. И ведь гордятся, наверное, этой халтурой.


<b>Торговые площадки (5 штук, 44 ФЗ)</b>
Это действительно приложения (не веб-клиенты). Но контроллеры полей мне не нравятся.
Примеры:
<img src="https://habrastorage.org/webt/ap/wl/in/apwlinkughplv39l_m4qsopioza.jpeg" />

Выровнено странно, ширина поля явно недостаточна, autoheight в поле ввода отсутствует.


Еще пример. В поле «Дата публикации» нет шаблона дд.мм.гггг, календарь с ошибкой, пиктограмма календаря пугает:
<img src="https://habrastorage.org/webt/zl/lk/6z/zllk6zxsfvaoxyq9qk7p9unosjw.jpeg" />


Список на rts-tender: есть выделенная цветом текущая запись, стрелками можно двигаться по списку, но нет автоскроллинга (можно убежать за границу экрана), ни Enter, ни пробел не открывают ссылку, табулятор не привязан к текущей записи. Хотя открыть ссылку можно только мышкой, я оцениваю контрол со знаком плюс. Такого функционала (запомнить и подсветить текущий документ) мне не хватает в mail.ru



Вроде как мелочи. Но профессиональное приложение отличается от полупрофессионального именно мелочами. Конечному пользователю наплевать, какая у вас БД и сколько слоев в концептуальной модели. Он работает с экранными формами и у него 3 требования: функционально, удобно, быстро.
К сожалению, выбор системы определяют айтишники и начальники, которые сами с системой не работали и работать не будут. Скорость они оценят, функциональность оценят так, как они это понимают, а на удобство им наплевать, главное, чтобы было красиво.
Павел Валерьевич Дуров не изобретал ни соцсеть, ни мессенджер. Он сделал то, что нужно пользователям, удобно и красиво. И люди это оценили, в том числе и материально.

Сова – это инструмент для построения профессионального интерфейса.
Что это значит, на примере СЭД.

Есть СЭД, в ней 3 группы пользователей:
Начальство
Специалисты, готовящие документы
Делопроизводители.

Начальники, они как дети. Им нужно, чтобы просто и красиво. В идеале 1 кнопка и 1 поле. И чтобы понтов побольше. Веб-клиент и, конечно, мобильный клиент, чтобы понты демонстрировать.

Специалисты. Веб-клиент, помесь соцсеть/почтовик. Не забывайте, что специалистов много и их надо обучать. Чем привычней будет для них среда, тем лучше. Мобильный клиент тоже пригодится, если служба безопасности разрешит.

Делопроизводители. Вот здесь пригодится Сова. Делопроизводители – это системообразующая группа пользователей. Все остальные могут заболеть/уйти в отпуск/перестать пользоваться, - СЭД будет работать. Если остановится регистрация, встанет все.
Делопроизводство – это конвейер, и здесь важно все, любая мелочь: шрифты, полутона, автоматическое заполнение, проверка значений, удобство ввода и т.д.
СЭД «Дело». Кабинеты исполнителей сделаны на веб-клиенте, канцелярия – толстый клиент. Все прекрасно, но это будет работать, пока правительство не запретит Windows в госструктурах. Я люблю Win 7, но если бы правителем был я, it-рынок взбодрился новыми заказами, а MS остался в светлой памяти. Кстати, 6 декабря Антон Силуанов подписал <a href="https://www.vedomosti.ru/technology/articles/2018/12/16/789399-siluanov-trebuet-ot-goskompanii">директиву</a> о переходе на отечественное ПО.


<b>Sova.online</b>

Как Сова открывает форму.
Без multi-page.
Центральным элементом Совы является компонент Document. Нажав ctrl-U на стартовой странице, вы увидите все, что нужно для создания объекта класса Document:
- данные полей БД;
- url формы для отображения;
- dbAlias, unid - для работы с БД;
- что-то там еще.

В некоторой степени Document – это аналог Redux-form.
Форма загружается в виде JSON строки, затем бывший словарь становится объектом, имеющим style, className и массив (список) элементов. Массив будет вставлен в элемент с id=root в виде <source lang="http"><div style className>…массив…</div></source>
Элемента массива - это объекты, описывающие теги <source lang="http"><div>, <a>, <img>, <button></source>, или массив, или компоненты.
За парсинг массива отвечает функция boxing. Если встретится элемент, содержащий массив, она рекурсивно вызовет себя.
Пуп земли, конечно, div.
В простейшем случае это строка: dict(div='Привет', className='h2')
Но может быть и массив (массив массивов):
<source lang="python">
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()] },
]),
]
)</source>

Здесь 3 панели (каждая в отдельном файле: subFormTop.py и т.д.).
subFormTop.panel() возвращает массив для построения верхней панели.
subFormLeft.panel() и subFormRight.panel() объединены в строку ('className': 'row') и описывают левую и правую панели.
subFormDown.panel() закомментирована (не пригодилась).

Возможно, покажется сложным. Но это Питон: все можно упростить.
Пример формы, из журнала «Отчеты». Функция labField(метка, имя_поля_БД) возвращает массив из двух словарей (строку таблицы): 1-й словарь – это {'div': метка}, второй {'field': [имя_поля_БД, 'tx']}.
<source lang="python">
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(),
]
</source>

<img src="https://habrastorage.org/webt/7u/ah/tw/7uahtweb5axodfbphubw5v3-okw.jpeg" />

Примеры из sova/api/forms/home/top.py (стартовая на sova.online):

питоновский словарь
{'a': 'React v16', 'href': 'https://reactjs.org'}
Породит понятную React компоненту
<source lang="http"><a href={'https://reactjs.org'}>React v16</a></source>

Img поумнее стандартного – в пропсах можно задать href и target:

Питон:
dict(img='image?react.ico', style={'width':16}, href='https://reactjs.org')

Фрагмент парсера, преобразующего массив объектов в компоненты (boxing.js):
<source lang="javascript">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;
}
</source>


Наберите в поисковике "react component library". Результат предсказуем, - очень много. Но все это изобилие предназначено веб-сайтам, не приложениям:
Smart-textarea – пожалуй, единственный контрол, который меня устроил.
React-select –упростил и переделал выпадающий список
Data-picker/calendar – не нашел ничего подходящего. Написал свой, взяв за образец встроенный в G.Chrome.
Upload / Download – ничего подходящего, написал свой.

Imho: у веб-сайтов грустное будущее. Подавляющее большинство пользователей в ближайшем будущем перестанет пользоваться браузерами (или уже перестало). Телефон срастется с планшетом, а 1 тире 10 приложений полностью обеспечат потребности.
Я уже дважды сталкивался с программистами, которые не знают, как правильно написать адрес e-mail. Зачем им помнить то, чем они не пользуются. Мир меняется.

В Сове контроллеры не идеальные, но они разрабатывались в расчете на оператора, а не веб-пользователя.
Как пример форма «Отметка о передаче». Достаточно универсальная форма, используется там, где есть начальник. Красным на скриншоте обведены поля, управляющие скрытием. Дополнительные резолюции открываются автоматически по мере заполнения, если в резолютивной части несколько поручений разным группам исполнителям с разными сроками. Два срока на группу: 1-й срок 1-му исполнителю, 2-й срок соисполнителям.
<img src="https://habrastorage.org/webt/yg/2q/f4/yg2qf4g9p3umqggkpduhnv4rgis.jpeg" />

Потрогать форму можно <a href="http://sova.online/docopen?SOVA/SITE&5C6945A82CB55D8E314456DA2FAB9A38&edit">ЗДЕСЬ</a>

Контроллер – это компонента React связанная с полем БД.
Подробное описание контроллеров с возможностью проверить их работу есть на Sova.online.
Обратите внимание на типы rtf и json. Rtf отображается как текст, но, если в тексте встретится конструкция {_ {объект} _}, Сова выполнит для этой конструкции json.parse и добавит результат в форму. Поле с типом json должно хранить описание массива элементов разметки: [{элем1}, {элем2}, …]. json.parse выполняется перед рендерингом.
Поля с такими типами позволяют хранить разметку в БД или в файлах. Полезно при формировании отчетов и написании документации.

Список контроллеров для всех типов полей (controllers.js):
<source lang="javascript">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}/>;
};
};
</source>

Для работы приложения необходим механизм манипуляции контроллерами.
В сове все контроллеры документа хранятся в переменной документа
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 прописаны обработчики
<source lang="javascript">recalc: {
PROJECTO: doc => doc.forceUpdate(),
WHOPRJ2: doc => doc.forceUpdate(),
WHOPRJ3: doc => doc.forceUpdate(),
… другие обработчики
}</source>
, а также прописаны условия скрытия (project, op, prj1, prj2…prj5 – это свойство "name" в описании div'ов):
<source lang="javascript">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')),
},</source>

Как это работает: поле 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':
<source lang="javascript">
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' },
},

}</source>

Валидация полей – функция, возвращает пусто, если все ОК, или сообщение о том, что не так. Сова установит фокус на невалидное поле.
Валидация формы – промис. В примере проверок нет (всегда вызывается yes), просто что-то выполняется перед отправкой на сервер.
В redux-form валидация сделана через trow – дикость какая-то.

<i>Для тех, кто не знаком с промисами, пример простейшего:</i>
<source lang="javascript">const confirmDlg = msg => new Promise((ok, cancel) => confirm(msg) ? ok('Нажата кнопка ОК') : cancel('Нажата кнопка cancel'));
confirmDlg('Самый простой промис')
.then( s => console.log(s))
.catch( s => console.log(s));</source>

Класс Document имеет несколько предопределенных команд, которые можно использовать в кнопках:

edit: перейти в режим редактирования формы
save: сохранить форму
close: закрыть форму
saveClose: сохранить и закрыть форму

prn: распечатать форму с выбором шаблона для печати
docOpen: открыть документ
dbOpen: открыть журнал
xopen: открыть url
newDoc: создать новый документ с нужной формой

В Redux-form api побогаче, - в Сове только необходимое.


<b>Multi-page.</b>

Класс Document создает объект (форму), который встраивается в элемент <source lang="http"><div id="root"></div></source>.
Назовем его «корневой документ». Если в корневой документ добавить элемент
<div style={{position: 'absolute', zIndex: 999}}/>, в него точно также можно вставить другой объект Document.
Как быть с подгружаемыми обработчиками команд? Все просто: каждая форма имеет свой обработчик (свой js), и корневой документ должен загрузить те из них, которые могут потребоваться.
Пример для стартовой страницы sova.online (home.py)
Форма home.py, чтобы продемонстрировать многостраничность, открывает документы с формами "rkckg", "outlet", "outlet.gru", "o".
Чтобы все формы корректно работали, необходимо прописать в home.py скрипты для этих форм:
<source lang="python">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']</source>

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

ООП и никаких чудес.

React – не React
Я уже описывал форму «report». Она открывается из менеджера отчетов (стрелка "React") и описывает параметры сбора отчета.
<img src="https://habrastorage.org/webt/rp/tb/vj/rptbvjkspfqes5nxti7wfmitbtu.jpeg" />
Сами отчеты (стрелка "не React") хранятся в подчиненных документах с формой «rreport» в виде html вложений. Мы занимались разработкой отчетов, когда React не было, форма "rreport" получилась простая (20 строк html и 15 строк просто-js), зачем менять то, что 8 лет работает.

<a href="http://sova.online/newdoc?RF_AKK_MOKRASG/STAT_KRD&reportManager">Открыть менеджер отчетов.</a>

Форма "rreport" состоит из 4-х кнопок и iframe. Сова перед открытием документа заменяет в айфрейме src="" на строку с url для скачивания html-вложения, остальное делает браузер.
С кнопками EXCEL / WORD аналогично: вставляем в нужное место кнопки url для скачивания с именем файла «report.html.xls» или «report.html.doc» и соответствующим mime-type. Остальное делают Excel/Word («эти умные животные отлично понимают все, что от них хотят» ).
Из do_get.py:

<source lang="python">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)</source>

При открытии html в Excel/Word отличия от браузера есть, но небольшие. Да и статья не об этом.

<b>Делаем форму с нуля.</b>

Исходные данные:
Есть 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'

Дата-время, затем уровень, затем в квадратных скобках категория внутри уровня, затем сообщение.

Задача:
Сделать страницу для просмотра лог-файла. Что типа
<img src="https://habrastorage.org/webt/du/6e/ni/du6enivag9amyf4ss1lucuf1tm8.jpeg" />

Назовем форму "lm", формироваться она будет функцией page в модуле api/forms/lm.py
<source lang="python">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)')
),
]
)</source>

В левой части 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
<source lang="python">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'</source>
logParser считывает и парсит log-файл. Результаты раскладывает в несколько массивов и сохраняет их в глобальном словаре. Ничего интересного: 2 простейших re и цикл по итератору.
Функции для работы с глобальным словарем:
toWell(o, key1, [key2]) - сохранить в глобальном словаре объект "o"
well(key1, [key2]) - взять из глобального словаря объект по ключу (по двум ключам).
Для первой прорисовки этого достаточно. Чтобы можно было отобразить сообщения нужного типа и нужной категории, необходимо сделать подгружаемый js.
В lm.py добавляем строку
javaScriptUrl = 'jsv?api/forms/lm/lm.js'
и создаем lm.js:
<source lang="javascript">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) );
};</source>

getLogData вытягивает с сервера сообщения нужного типа и нужной категории:
<source lang="python">def getLogData(par, un):
lg, _, cat = par.partition('|')
msg = well('logger_' + lg, cat)
return 200, 'text/html; charset=UTF-8', '\n'.join(reversed(msg))
</source>

Насладиться формой можно <a href="http://sova.online/newdoc?&lm">ЗДЕСЬ</a>.
Изначально логгирование было сделано на основе стандартного модуля 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 – документ (объект, содержащий любое количество полей).


<b>Установка: </b>
Скачать с http://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, детка. Обработчики команд должны быть внутри формы, а не как рога у оленя
- зачем ты вообще статью написал?
- пиарюсь