Конструктор ERP Python+Django

Автор 1ex, 27 марта 2025, 18:39

« назад - далее »

1ex

Переехал с 1cpp. Ссылку публиковать не буду, форум умер :(

Приветствую коллеги!

Потихоньку разрабатываю замену платформы 1С 7.7 + 1cpp.  Пока исключительно для себя и компании в которой работаю.

Успешно получалось долгое время обходить все болячки и технические долги во многом очень удачной платформы. Но в итоге уткнулся таки в ограничения. Переход на 8 - привел бы к таким-же последствиям.

Основным фактором, как это не странно, стал постепенный переход на Битрикс24. Пользователи активно начали работать с браузером. Понадобились синхронизации (выгрузки - загрузки), чтобы исключить двойную работу. Не считая установки двух мониторов, чтобы удобнее было работать в 1С и в Битрикс... Ну и массу других вещей...

Короче захотелось странного.

1. Нужен был оперативный конструктор, позволяющий редактировать структуру справочников и документов на лету.
2. Нужны были сложные структуры данных - типа деревьев, словарей (составных реквизитов), в том числе и в документах.
3. Нужны были документы с контролем ошибок, до того момента ,как это попадет в базу...
4. Все действия пользователя с данными - должны быть в истории, и это было свойством платформы на любых уровнях.
5. Чтобы кода поменьше.
6. Расширений и библиотек  побольше.
7. JavaScript для управления формами.
8. И все это в браузере...

Короче.
Вроде бы концепт получился...

Разработка еще займет кучу времени.
Идей еще много, чтобы включить это в платформу, но пока могу дать первую версию, на которой уже можно сделать очень много.
Ссылка на проект тут
https://github.com/fobyphill/forTea
Недавно обновил проект.
Документация отстает - как обычно.

Злоп

Что с многопользовательской работой? что с блокировками/транзакциями?

Злоп

насколько я понимаю - установка неуспешно
ModuleNotFoundError: No module named 'distutils'
Launch unsuccessful. Exiting.
Для продолжения нажмите любую клавишу . . .

Злоп

python
E:\00\venv
""
Creating venv in directory E:\00\venv using python "D:\Program Files\python.exe"
Install requirements to venv
activate
start pip
Unable to install requirements

exit code: 2

stdout:
Collecting asgiref==3.5.0 (from -r requirements.txt (line 1))
  Using cached asgiref-3.5.0-py3-none-any.whl.metadata (9.2 kB)
Collecting backports.zoneinfo==0.2.1 (from -r requirements.txt (line 2))
  Using cached backports.zoneinfo-0.2.1.tar.gz (74 kB)
  Installing build dependencies: started
  Installing build dependencies: finished with status 'done'
  Getting requirements to build wheel: started
  Getting requirements to build wheel: finished with status 'done'
  Preparing metadata (pyproject.toml): started
  Preparing metadata (pyproject.toml): finished with status 'done'
Collecting certifi==2021.10.8 (from -r requirements.txt (line 3))
  Using cached certifi-2021.10.8-py2.py3-none-any.whl.metadata (3.0 kB)
Collecting charset-normalizer==2.0.12 (from -r requirements.txt (line 4))
  Using cached charset_normalizer-2.0.12-py3-none-any.whl.metadata (11 kB)
Collecting contourpy==1.0.7 (from -r requirements.txt (line 5))
  Using cached contourpy-1.0.7.tar.gz (13.4 MB)
  Installing build dependencies: started
  Installing build dependencies: finished with status 'done'
  Getting requirements to build wheel: started
  Getting requirements to build wheel: finished with status 'done'
  Preparing metadata (pyproject.toml): started
  Preparing metadata (pyproject.toml): finished with status 'done'
Collecting cycler==0.11.0 (from -r requirements.txt (line 6))
  Using cached cycler-0.11.0-py3-none-any.whl.metadata (785 bytes)
Collecting DateTime==4.4 (from -r requirements.txt (line 7))
  Using cached DateTime-4.4-py2.py3-none-any.whl.metadata (32 kB)
Collecting Django==4.0.2 (from -r requirements.txt (line 8))
  Using cached Django-4.0.2-py3-none-any.whl.metadata (4.0 kB)
Collecting fonttools==4.39.4 (from -r requirements.txt (line 9))
  Using cached fonttools-4.39.4-py3-none-any.whl.metadata (146 kB)
Collecting idna==3.3 (from -r requirements.txt (line 10))
  Using cached idna-3.3-py3-none-any.whl.metadata (9.8 kB)
Collecting importlib-resources==5.12.0 (from -r requirements.txt (line 11))
  Using cached importlib_resources-5.12.0-py3-none-any.whl.metadata (4.1 kB)
Collecting kiwisolver==1.4.4 (from -r requirements.txt (line 12))
  Using cached kiwisolver-1.4.4.tar.gz (97 kB)
  Installing build dependencies: started
  Installing build dependencies: finished with status 'done'
  Getting requirements to build wheel: started
  Getting requirements to build wheel: finished with status 'done'
  Preparing metadata (pyproject.toml): started
  Preparing metadata (pyproject.toml): finished with status 'done'
Collecting matplotlib==3.7.1 (from -r requirements.txt (line 13))
  Using cached matplotlib-3.7.1.tar.gz (38.0 MB)
  Installing build dependencies: started
  Installing build dependencies: finished with status 'done'
  Getting requirements to build wheel: started
  Getting requirements to build wheel: finished with status 'done'
  Preparing metadata (pyproject.toml): started
  Preparing metadata (pyproject.toml): finished with status 'done'
Collecting mysqlclient==2.1.0 (from -r requirements.txt (line 14))
  Using cached mysqlclient-2.1.0.tar.gz (87 kB)
  Installing build dependencies: started
  Installing build dependencies: finished with status 'done'
  Getting requirements to build wheel: started
  Getting requirements to build wheel: finished with status 'done'
  Preparing metadata (pyproject.toml): started
  Preparing metadata (pyproject.toml): finished with status 'done'
Collecting networkx==3.1 (from -r requirements.txt (line 15))
  Using cached networkx-3.1-py3-none-any.whl.metadata (5.3 kB)
Collecting numpy==1.22.4 (from -r requirements.txt (line 16))
  Using cached numpy-1.22.4.zip (11.5 MB)
  Installing build dependencies: started
  Installing build dependencies: finished with status 'done'
  Getting requirements to build wheel: started
  Getting requirements to build wheel: finished with status 'done'

stderr:

[notice] A new release of pip is available: 24.3.1 -> 25.0.1
[notice] To update, run: python.exe -m pip install --upgrade pip
ERROR: Exception:
Traceback (most recent call last):
  File "E:\00\venv\Lib\site-packages\pip\_internal\cli\base_command.py", line 105, in _run_wrapper
    status = _inner_run()
  File "E:\00\venv\Lib\site-packages\pip\_internal\cli\base_command.py", line 96, in _inner_run
    return self.run(options, args)
           ~~~~~~~~^^^^^^^^^^^^^^^
  File "E:\00\venv\Lib\site-packages\pip\_internal\cli\req_command.py", line 67, in wrapper
    return func(self, options, args)
  File "E:\00\venv\Lib\site-packages\pip\_internal\commands\install.py", line 379, in run
    requirement_set = resolver.resolve(
        reqs, check_supported_wheels=not options.target_dir
    )
  File "E:\00\venv\Lib\site-packages\pip\_internal\resolution\resolvelib\resolver.py", line 95, in resolve
    result = self._result = resolver.resolve(
                            ~~~~~~~~~~~~~~~~^
        collected.requirements, max_rounds=limit_how_complex_resolution_can_be
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "E:\00\venv\Lib\site-packages\pip\_vendor\resolvelib\resolvers.py", line 546, in resolve
    state = resolution.resolve(requirements, max_rounds=max_rounds)
  File "E:\00\venv\Lib\site-packages\pip\_vendor\resolvelib\resolvers.py", line 397, in resolve
    self._add_to_criteria(self.state.criteria, r, parent=None)
    ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "E:\00\venv\Lib\site-packages\pip\_vendor\resolvelib\resolvers.py", line 173, in _add_to_criteria
    if not criterion.candidates:
           ^^^^^^^^^^^^^^^^^^^^
  File "E:\00\venv\Lib\site-packages\pip\_vendor\resolvelib\structs.py", line 156, in __bool__
    return bool(self._sequence)
  File "E:\00\venv\Lib\site-packages\pip\_internal\resolution\resolvelib\found_candidates.py", line 174, in __bool__
    return any(self)
  File "E:\00\venv\Lib\site-packages\pip\_internal\resolution\resolvelib\found_candidates.py", line 162, in <genexpr>
    return (c for c in iterator if id(c) not in self._incompatible_ids)
                       ^^^^^^^^
  File "E:\00\venv\Lib\site-packages\pip\_internal\resolution\resolvelib\found_candidates.py", line 53, in _iter_built
    candidate = func()
  File "E:\00\venv\Lib\site-packages\pip\_internal\resolution\resolvelib\factory.py", line 187, in _make_candidate_from_link
    base: Optional[BaseCandidate] = self._make_base_candidate_from_link(
                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        link, template, name, version
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "E:\00\venv\Lib\site-packages\pip\_internal\resolution\resolvelib\factory.py", line 233, in _make_base_candidate_from_link
    self._link_candidate_cache[link] = LinkCandidate(
                                       ~~~~~~~~~~~~~^
        link,
        ^^^^^
    ...<3 lines>...
        version=version,
        ^^^^^^^^^^^^^^^^
    )
    ^
  File "E:\00\venv\Lib\site-packages\pip\_internal\resolution\resolvelib\candidates.py", line 304, in __init__
    super().__init__(
    ~~~~~~~~~~~~~~~~^
        link=link,
        ^^^^^^^^^^
    ...<4 lines>...
        version=version,
        ^^^^^^^^^^^^^^^^
    )
    ^
  File "E:\00\venv\Lib\site-packages\pip\_internal\resolution\resolvelib\candidates.py", line 159, in __init__
    self.dist = self._prepare()
                ~~~~~~~~~~~~~^^
  File "E:\00\venv\Lib\site-packages\pip\_internal\resolution\resolvelib\candidates.py", line 236, in _prepare
    dist = self._prepare_distribution()
  File "E:\00\venv\Lib\site-packages\pip\_internal\resolution\resolvelib\candidates.py", line 315, in _prepare_distribution
    return preparer.prepare_linked_requirement(self._ireq, parallel_builds=True)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "E:\00\venv\Lib\site-packages\pip\_internal\operations\prepare.py", line 527, in prepare_linked_requirement
    return self._prepare_linked_requirement(req, parallel_builds)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File "E:\00\venv\Lib\site-packages\pip\_internal\operations\prepare.py", line 642, in _prepare_linked_requirement
    dist = _get_prepared_distribution(
        req,
    ...<3 lines>...
        self.check_build_deps,
    )
  File "E:\00\venv\Lib\site-packages\pip\_internal\operations\prepare.py", line 72, in _get_prepared_distribution
    abstract_dist.prepare_distribution_metadata(
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        finder, build_isolation, check_build_deps
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "E:\00\venv\Lib\site-packages\pip\_internal\distributions\sdist.py", line 56, in prepare_distribution_metadata
    self._install_build_reqs(finder)
    ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
  File "E:\00\venv\Lib\site-packages\pip\_internal\distributions\sdist.py", line 126, in _install_build_reqs
    build_reqs = self._get_build_requires_wheel()
  File "E:\00\venv\Lib\site-packages\pip\_internal\distributions\sdist.py", line 103, in _get_build_requires_wheel
    return backend.get_requires_for_build_wheel()
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "E:\00\venv\Lib\site-packages\pip\_internal\utils\misc.py", line 701, in get_requires_for_build_wheel
    return super().get_requires_for_build_wheel(config_settings=cs)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
  File "E:\00\venv\Lib\site-packages\pip\_vendor\pyproject_hooks\_impl.py", line 166, in get_requires_for_build_wheel
    return self._call_hook('get_requires_for_build_wheel', {
           ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        'config_settings': config_settings
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    })
    ^^
  File "E:\00\venv\Lib\site-packages\pip\_vendor\pyproject_hooks\_impl.py", line 321, in _call_hook
    raise BackendUnavailable(data.get('traceback', ''))
pip._vendor.pyproject_hooks._impl.BackendUnavailable: Traceback (most recent call last):
  File "E:\00\venv\Lib\site-packages\pip\_vendor\pyproject_hooks\_in_process\_in_process.py", line 77, in _build_backend
    obj = import_module(mod_path)
  File "D:\Program Files\Lib\importlib\__init__.py", line 88, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1310, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 1026, in exec_module
  File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
  File "E:\TEMP\pip-build-env-kbzfw4bd\overlay\Lib\site-packages\setuptools\__init__.py", line 10, in <module>
    import distutils.core
ModuleNotFoundError: No module named 'distutils'


Launch unsuccessful. Exiting.
Для продолжения нажмите любую клавишу . . .

1ex

Цитата: Злоп от 28 марта 2025, 00:32насколько я понимаю - установка неуспешно
ModuleNotFoundError: No module named 'distutils'
Launch unsuccessful. Exiting.
Для продолжения нажмите любую клавишу . . .

Спасибо.
Прошу меня простить.
Сегодня постараюсь поправить - добавил новый модуль, и забыл его в requirements.txt включить.

Цитата: Злоп от 28 марта 2025, 00:14Что с многопользовательской работой? что с блокировками/транзакциями?

Обычная "web-морда" с асинхронным поведением.
Поэтому многопользовательская работа в принципе нормальная.
Транзакции - на каждое изменение, плюс логирование всех изменений - как часть платформы.

Блокировки делать не стал :(. У меня на них многолетняя трудовая аллергия.
Выкрутился через очередь транзакций и маниакальное логирование каждого реквизита.

Смысл такой: При одновременной работе с одним и тем же объектом каждое изменение сохраняется в истории с временем и пользователем. Если два пользователя решили поменять наименование в справочнике - то оба их варианта сохранятся. "Правильность" определяется - кто последний - тот и "виновник". И каждое из изменений с помощью "ползунка таймлайна" - можно сделать актуальным.

А кто из- них прав в итоге - пусть пользователи выясняют друг с другом.
(Знаю что это неприятно (и как 1с-ник понимаю что "истина где-то рядом") - но система то, тут причем? Не знаю как вы - но если я получу сообщение - что "объект заблокирован" - меня это больше растраивает, чем гипотетическая правда о том что система "так защищает ваши данные". Имхо - Все данные - важны. Потеря данных при попытке их записать - это гораздо хуже. Опять же имхо. Не нарываюсь на холивар...)


Злоп

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

Злоп

Пока только на лицо хочу посмотреть как это выглядит.
Никике джавы учить не хочу.

1ex

Никаких джав - чесслово.
Скинул в личку ссылку на демку.

1ex

Цитата: Злоп от 28 марта 2025, 15:22Я могу наложить транзакцию, + включить блокировку, выполнить кучу действий с кучей объектов, зафиксировать транзакцию и только потом выключить блокировку. У меня есть варианты когда я даже не должен допускать грязное чтение кучи объектов...
?

У меня таких вариантов тоже есть "море". Но, пока обхожусь (обтекаю)... Спорить не буду. Блокировки бывают например нужны для исключения конкуренции за объект. Но и прямо сильной надобности не вижу.

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

amo|obs

Почему только Злопу?
Я тоже глянуть хочу.

1ex

Йех.
(с закрытыми глазами давлю на кнопки)
База на сервере в теории - должна пересоздаваться раз в день. И это не факт.
Сервер тут: fortea2.ru

Админский доступ - дает доступ к конфигуратору и ко всему - что админ может делать (включая создание юзеров - через админку Django).

odmen
qweqwe123123

Юзер - ограничен в области видимости - может создавать только документы (в проекте - называются Контракты).

just_user
poiPOI098


Продвинутый юзер - может редактировать справочники и контракты.
power_user
sadhfjwei

Прошу ничего сильно не ломать.
А так - велкам.


Злоп

Цитата: 1ex от 28 марта 2025, 15:41Блокировки бывают например нужны для исключения конкуренции за объект.
ПроцессN:
Стартанули. Точка А
прочитали кучу данных из кучи разных таблиц.
сделали кучу вычислений.
записали кучу данных в кучу разных таблиц
Финиш. Точка Б.
.
надо чтобы пока не отработает процесс1 от А до Б - чтобы аналогичный процесс2 (который будет использовать те же самые объекты от А до Б) ждал (или получил отлуп).
.
Реализуемо?
.
в клюшках просто выставлял логическую блокировку (если она еще не была выставлена, а если уже стояла - то ждал/отлуп), делал что надо, снимал блокировку.

Злоп

Цитата: 1ex от 28 марта 2025, 15:33Никаких джав - чесслово.
Скинул в личку ссылку на демку.

Посмотрел бегло.
Жуть.
Фейс ни в дугу.
Как сырец-демо что в принципе что-то можно сделать - может быть... в принципе...
Фейс растянутый адски, похож на перспективный фейс 8-ки, от которого все шарахаются ;-)
Но я злоп, с годами стал требователен к красоте мира, поэтому мое мнение так себе...

1ex

Цитата: Злоп от 28 марта 2025, 23:47
Цитата: 1ex от 28 марта 2025, 15:41Блокировки бывают например нужны для исключения конкуренции за объект.
ПроцессN:
Стартанули. Точка А
прочитали кучу данных из кучи разных таблиц.
сделали кучу вычислений.
записали кучу данных в кучу разных таблиц
Финиш. Точка Б.
.
надо чтобы пока не отработает процесс1 от А до Б - чтобы аналогичный процесс2 (который будет использовать те же самые объекты от А до Б) ждал (или получил отлуп).
.
Реализуемо?
.
в клюшках просто выставлял логическую блокировку (если она еще не была выставлена, а если уже стояла - то ждал/отлуп), делал что надо, снимал блокировку.

Сделал такое через "каскадные таски". Смысл в создании вложенных "умных" транзакций. Все как вы и описали. Умных - имею в виду, что это иерархия полноценных задача, с кодом условий (в основном шаблонированах - чтобы не писать код условий типа: Если таск такой-то - завершился, то стартуем другой.)
Я для разработки написал документик по Таскам. Вот - делюсь, можно кидать тапками: https://disk.yandex.ru/i/uakeZ9H7x6HnVw
Может не все еще работает - но таков путь :)

Цитата: Злоп от 28 марта 2025, 23:49Посмотрел бегло.
Жуть.
Фейс ни в дугу.
Как сырец-демо что в принципе что-то можно сделать - может быть... в принципе...
Фейс растянутый адски, похож на перспективный фейс 8-ки, от которого все шарахаются ;-)
Но я злоп, с годами стал требователен к красоте мира, поэтому мое мнение так себе...

Спасибо, что вообще посмотрели :)
(Ща, сочиню слезливую историю о том, почему мы такие убогие :) )
Начало анекдота...
Собрались как-то математик, 1с-овец (я), и бакендер (Фил).

Я, вместе с математиком, написал Суперсложную Фигню стройную теорию. которую положил на хабр.
Если не получится осилить сотни две (три), формул - не ходите сюда:
https://habr.com/ru/articles/755158/
Бакендер сказал - какая Суперсложная Фигня стройная теория, ща запилим.
Выкатил в итоге ядро, и спросил: - А фронт вообще нужен?
Я почесал в затылке долго думал, и сказал: Давай сделаем шаблон из нескольких HTML страниц с каким-нибудь базовым CSS. Если вообще кому-нибудь кроме нас Эта СФ этот хороший проект понадобится - то из-за открытых исходников могут подправить шаблоны страниц так, чтобы не вызывать резь в глазах. А нам "внутри" и так хватит. Я к сожалению для остальных - предпочитаю функциональность - красоте.

Зачем я это все: Фронтендера у меня пока нету - и когда появится - непонятно.
А в разработке у меня принцип: Устойчивость - Функциональность - Безопасность - Красивости и плюшки. Так-что доберусь когда-нибудь.


1ex

На гитхабе поправил проект (база - с простенькой демкой).

Старую версию - удалить.
Качаем, распаковываем, ничего вроде править не надо.

start.bat

ждем (надеюсь) вот такого :)

"Starting development server at http://127.0.0.1:8000/"

admin/admin