Merge branch 'develop'
- add repository project odoo 18 - improve selenium - script install with debug - support queue_job when running robotlibre - mobile support context name and rename repo - script to kill odoo process from TODO
This commit is contained in:
commit
da227b8d28
23 changed files with 2409 additions and 947 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -49,4 +49,4 @@ screenshots
|
|||
screencasts
|
||||
private/todo/todo_override_private.json
|
||||
.erplibre.error.txt
|
||||
/mobile/technolibre_home_mobile/
|
||||
/mobile/erplibre_home_mobile/
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ robot_libre_init:
|
|||
.PHONY: robot_libre_pre
|
||||
robot_libre_pre:
|
||||
echo "Create database robotlibre"
|
||||
./script/config/setup_odoo_config_conf_devops.py
|
||||
./script/database/db_restore.py --database robotlibre
|
||||
|
||||
.PHONY: robot_libre
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ sudo snap install core
|
|||
sudo snap refresh core
|
||||
|
||||
# https://certbot.eff.org/lets-encrypt/debianbuster-nginx
|
||||
# Cerbot
|
||||
# Certbot
|
||||
sudo snap install --classic certbot
|
||||
sudo ln -s /snap/bin/certbot /usr/bin/certbot
|
||||
```
|
||||
|
|
|
|||
|
|
@ -69,6 +69,8 @@ services:
|
|||
- selenium-hub
|
||||
environment:
|
||||
- SE_EVENT_BUS_HOST=selenium-hub
|
||||
# - SE_NODE_OVERRIDE_MAX_SESSIONS=true
|
||||
# - SE_NODE_MAX_SESSIONS=2
|
||||
|
||||
# put into config.conf selenium_network_url=http://selenium-hub:4444
|
||||
# in python
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
<project
|
||||
name="technolibre_home_mobile.git"
|
||||
path="mobile/technolibre_home_mobile"
|
||||
path="mobile/erplibre_home_mobile"
|
||||
remote="TechnoLibre"
|
||||
revision="main"
|
||||
groups="mobile"
|
||||
|
|
|
|||
|
|
@ -284,6 +284,13 @@
|
|||
remote="ERPLibre_origin_MathBenTech"
|
||||
groups="addons,base,odoo16.0"
|
||||
/>
|
||||
<project
|
||||
name="design-themes.git"
|
||||
path="odoo16.0/addons/odoo_design-themes"
|
||||
remote="ERPLibre_origin_odoo"
|
||||
revision="16.0"
|
||||
groups="addons,odoo16.0"
|
||||
/>
|
||||
<project
|
||||
name="donation.git"
|
||||
revision="16.0"
|
||||
|
|
|
|||
|
|
@ -262,6 +262,13 @@
|
|||
remote="ERPLibre_origin_MathBenTech"
|
||||
groups="addons,base,odoo17.0"
|
||||
/>
|
||||
<project
|
||||
name="design-themes.git"
|
||||
path="odoo17.0/addons/odoo_design-themes"
|
||||
remote="ERPLibre_origin_odoo"
|
||||
revision="17.0"
|
||||
groups="addons,odoo17.0"
|
||||
/>
|
||||
<project
|
||||
name="donation.git"
|
||||
path="odoo17.0/addons/OCA_donation"
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@
|
|||
<remote name="ERPLibre_origin_OCA" fetch="https://github.com/ERPLibre/" />
|
||||
<remote name="ERPLibre_origin_CybroOdoo" fetch="https://github.com/ERPLibre/" />
|
||||
<remote name="ERPLibre_origin_TechnoLibre" fetch="https://github.com/ERPLibre/" />
|
||||
<remote name="ERPLibre_origin_ONLYOFFICE" fetch="https://github.com/ERPLibre/" />
|
||||
<remote name="Numigi" fetch="https://github.com/Numigi/" />
|
||||
<remote name="OCA" fetch="https://github.com/OCA/" />
|
||||
<remote name="ONLYOFFICE" fetch="https://github.com/ONLYOFFICE" />
|
||||
<remote name="OmniaGit" fetch="https://github.com/OmniaGit/" />
|
||||
<remote name="TechnoLibre" fetch="https://github.com/TechnoLibre/" />
|
||||
<remote name="ajepe" fetch="https://github.com/ajepe/" />
|
||||
|
|
@ -55,9 +57,9 @@
|
|||
/>
|
||||
<project
|
||||
name="account-budgeting.git"
|
||||
revision="18.0-mig-account_budget_oca"
|
||||
revision="18.0"
|
||||
path="odoo18.0/addons/OCA_account-budgeting"
|
||||
remote="ERPLibre_origin_OCA"
|
||||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
|
|
@ -123,6 +125,20 @@
|
|||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="agreement.git"
|
||||
revision="18.0"
|
||||
path="odoo18.0/addons/OCA_agreement"
|
||||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="ai.git"
|
||||
revision="18.0"
|
||||
path="odoo18.0/addons/OCA_ai"
|
||||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="apps-store.git"
|
||||
revision="18.0"
|
||||
|
|
@ -130,6 +146,13 @@
|
|||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="automation.git"
|
||||
revision="18.0"
|
||||
path="odoo18.0/addons/OCA_automation"
|
||||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="bank-payment.git"
|
||||
revision="18.0"
|
||||
|
|
@ -180,7 +203,13 @@
|
|||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<!-- <project name="cetmix-tower.git" revision="18.0" path="odoo18.0/addons/cetmix_cetmix-tower" remote="cetmix" groups="addons,odoo18.0" />-->
|
||||
<project
|
||||
name="cetmix-tower.git"
|
||||
revision="18.0"
|
||||
path="odoo18.0/addons/cetmix_cetmix-tower"
|
||||
remote="cetmix"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="community-data-files.git"
|
||||
revision="18.0"
|
||||
|
|
@ -282,6 +311,13 @@
|
|||
remote="ERPLibre_origin_MathBenTech"
|
||||
groups="addons,base,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="design-themes.git"
|
||||
path="odoo18.0/addons/odoo_design-themes"
|
||||
remote="ERPLibre_origin_odoo"
|
||||
revision="18.0"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="donation.git"
|
||||
revision="18.0"
|
||||
|
|
@ -328,6 +364,8 @@
|
|||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project name="hr.git" revision="18.0" path="odoo18.0/addons/OCA_hr" remote="OCA" groups="addons,odoo18.0" />
|
||||
<project name="hr-attendance.git" revision="18.0" path="odoo18.0/addons/OCA_hr-attendance" remote="OCA" groups="addons,odoo18.0" />
|
||||
<project name="hr-expense.git" revision="18.0" path="odoo18.0/addons/OCA_hr-expense" remote="OCA" groups="addons,odoo18.0" />
|
||||
<project
|
||||
name="hr-holidays.git"
|
||||
revision="18.0"
|
||||
|
|
@ -335,6 +373,20 @@
|
|||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="infrastructure.git"
|
||||
revision="18.0"
|
||||
path="odoo18.0/addons/OCA_infrastructure"
|
||||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="intrastat-extrastat.git"
|
||||
revision="18.0"
|
||||
path="odoo18.0/addons/OCA_intrastat-extrastat"
|
||||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="interface-github.git"
|
||||
revision="18.0"
|
||||
|
|
@ -342,6 +394,13 @@
|
|||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="iot.git"
|
||||
revision="18.0"
|
||||
path="odoo18.0/addons/OCA_iot"
|
||||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="knowledge.git"
|
||||
revision="18.0"
|
||||
|
|
@ -364,6 +423,7 @@
|
|||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project name="mail.git" revision="18.0" path="odoo18.0/addons/OCA_mail" remote="OCA" groups="addons,odoo18.0" />
|
||||
<project name="mass-mailing.git" revision="18.0" path="odoo18.0/addons/OCA_mass-mailing" remote="OCA" groups="addons,odoo18.0" />
|
||||
<project
|
||||
name="management-system.git"
|
||||
revision="18.0"
|
||||
|
|
@ -378,6 +438,13 @@
|
|||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="manufacture-reporting.git"
|
||||
revision="18.0"
|
||||
path="odoo18.0/addons/OCA_manufacture-reporting"
|
||||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="margin-analysis.git"
|
||||
revision="18.0"
|
||||
|
|
@ -400,6 +467,13 @@
|
|||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="muk-it_odoo-modules.git"
|
||||
path="odoo18.0/addons/muk-it_odoo-modules"
|
||||
remote="TechnoLibre_origin_muk-it"
|
||||
revision="18.0"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<!-- <project name="muk_web.git" revision="18.0" path="odoo18.0/addons/muk-it_muk_web" remote="muk-it" groups="addons,odoo18.0" />-->
|
||||
<!-- <project-->
|
||||
<!-- name="odoo-business-spending-management-quebec-canada.git"-->
|
||||
|
|
@ -436,10 +510,10 @@
|
|||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="muk-it_odoo-modules.git"
|
||||
path="odoo18.0/addons/muk-it_odoo-modules"
|
||||
remote="TechnoLibre_origin_muk-it"
|
||||
name="odoo-pim.git"
|
||||
revision="18.0"
|
||||
path="odoo18.0/addons/OCA_odoo-pim"
|
||||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
|
|
@ -464,6 +538,13 @@
|
|||
remote="OmniaGit"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="onlyoffice_odoo.git"
|
||||
revision="18.0"
|
||||
path="odoo18.0/addons/ONLYOFFICE_onlyoffice_odoo"
|
||||
remote="ERPLibre_origin_ONLYOFFICE"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="openeducat_erp.git"
|
||||
revision="18.0"
|
||||
|
|
@ -485,6 +566,13 @@
|
|||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="payroll.git"
|
||||
revision="18.0"
|
||||
path="odoo18.0/addons/OCA_payroll"
|
||||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project name="pos.git" revision="18.0" path="odoo18.0/addons/OCA_pos" remote="OCA" groups="addons,odoo18.0" />
|
||||
<project
|
||||
name="product-attribute.git"
|
||||
|
|
@ -535,6 +623,13 @@
|
|||
remote="ERPLibre_origin_OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="purchase-reporting.git"
|
||||
revision="18.0"
|
||||
path="odoo18.0/addons/OCA_purchase-reporting"
|
||||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="purchase-workflow.git"
|
||||
revision="18.0"
|
||||
|
|
@ -543,6 +638,20 @@
|
|||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project name="queue.git" revision="18.0" path="odoo18.0/addons/OCA_queue" remote="OCA" groups="addons,odoo18.0" />
|
||||
<project
|
||||
name="repair.git"
|
||||
revision="18.0"
|
||||
path="odoo18.0/addons/OCA_repair"
|
||||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="report-print-send.git"
|
||||
revision="18.0"
|
||||
path="odoo18.0/addons/OCA_report-print-send"
|
||||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="reporting-engine.git"
|
||||
revision="18.0"
|
||||
|
|
@ -550,6 +659,13 @@
|
|||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="rest-framework.git"
|
||||
revision="18.0"
|
||||
path="odoo18.0/addons/OCA_rest-framework"
|
||||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project name="rma.git" revision="18.0" path="odoo18.0/addons/OCA_rma" remote="OCA" groups="addons,odoo18.0" />
|
||||
<!-- <project-->
|
||||
<!-- name="saas-addons.git"-->
|
||||
|
|
@ -557,6 +673,27 @@
|
|||
<!-- remote="it-projects-llc"-->
|
||||
<!-- groups="addons,odoo18.0"-->
|
||||
<!-- />-->
|
||||
<project
|
||||
name="sale-blanket.git"
|
||||
revision="18.0"
|
||||
path="odoo18.0/addons/OCA_sale-blanket"
|
||||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="sale-channel.git"
|
||||
revision="18.0"
|
||||
path="odoo18.0/addons/OCA_sale-channel"
|
||||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="sale-promotion.git"
|
||||
revision="18.0"
|
||||
path="odoo18.0/addons/OCA_sale-promotion"
|
||||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="sale-reporting.git"
|
||||
revision="18.0"
|
||||
|
|
@ -613,13 +750,13 @@
|
|||
remote="ERPLibre_origin_OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<!-- <project-->
|
||||
<!-- name="shift-planning.git"-->
|
||||
<!-- revision="18.0" path="odoo18.0/addons/OCA_shift-planning"-->
|
||||
<!-- remote="ERPLibre"-->
|
||||
<!-- revision="18.0_dev"-->
|
||||
<!-- groups="addons,odoo18.0"-->
|
||||
<!-- />-->
|
||||
<project
|
||||
name="shift-planning.git"
|
||||
revision="18.0"
|
||||
path="odoo18.0/addons/OCA_shift-planning"
|
||||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project name="sign.git" revision="18.0" path="odoo18.0/addons/OCA_sign" remote="OCA" groups="addons,odoo18.0" />
|
||||
<project
|
||||
name="spreadsheet.git"
|
||||
|
|
@ -628,6 +765,13 @@
|
|||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="shopfloor-app.git"
|
||||
revision="18.0"
|
||||
path="odoo18.0/addons/OCA_shopfloor-app"
|
||||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="social.git"
|
||||
revision="18.0"
|
||||
|
|
@ -705,6 +849,13 @@
|
|||
remote="ERPLibre_origin_OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="web-api.git"
|
||||
revision="18.0"
|
||||
path="odoo18.0/addons/OCA_web-api"
|
||||
remote="OCA"
|
||||
groups="addons,odoo18.0"
|
||||
/>
|
||||
<project
|
||||
name="website-cms.git"
|
||||
revision="18.0"
|
||||
|
|
|
|||
|
|
@ -1,16 +1,11 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
if [[ ! -d "./mobile/technolibre_home_mobile" ]]; then
|
||||
if [[ ! -d "./mobile/erplibre_home_mobile" ]]; then
|
||||
echo "Please, run installation ./mobile/install_mobile_dev.sh before run this script ./mobile/compile_and_run.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -d "./mobile/technolibre_home_mobile/technolibre_home" ]]; then
|
||||
echo "Please, check repo is working."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd mobile/technolibre_home_mobile/technolibre_home
|
||||
cd mobile/erplibre_home_mobile
|
||||
|
||||
npm install
|
||||
npm run build && npx cap sync
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ python-dateutil
|
|||
unidecode
|
||||
sshconf
|
||||
cloudflare
|
||||
psutil
|
||||
|
||||
# For mobile
|
||||
pillow
|
||||
|
||||
# Force upgrade package to fix Ubuntu 25.04
|
||||
virtualenv==20.30.0
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -19,33 +19,44 @@ authors = [ "Mathieu Benoit <mathben@technolibre.ca>",]
|
|||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.12.10,<3.13"
|
||||
a2wsgi = ">=1.10.6"
|
||||
ansi2html = "^1.9.2"
|
||||
apispec = "^6.9.0"
|
||||
asn1crypto = "1.5.1"
|
||||
astor = "^0.8.1"
|
||||
avalara = "^25.11.0"
|
||||
avalara = "^25.12.0"
|
||||
babel = "2.10.3"
|
||||
bandit = "^1.9.2"
|
||||
bandit = "^1.9.3"
|
||||
base64io = "^1.0.3"
|
||||
beautifulsoup4 = "4.13.5"
|
||||
bokeh = "3.6.3"
|
||||
boto3 = "^1.42.1"
|
||||
cachetools = "^6.2.2"
|
||||
boto3 = "^1.42.34"
|
||||
cachetools = "^6.2.4"
|
||||
cairosvg = "^2.8.2"
|
||||
caldav = "1.3.9"
|
||||
cbor2 = "5.6.2"
|
||||
cerberus = "^1.3.8"
|
||||
chardet = "5.2.0"
|
||||
code-writer = "^1.2.0"
|
||||
code-writer = "^1.3.0"
|
||||
colorama = "^0.4.6"
|
||||
contextvars = "^2.4"
|
||||
cryptography = "46.0.3"
|
||||
cssselect = "^1.3.0"
|
||||
cython = "^3.2.2"
|
||||
cython = "^3.2.4"
|
||||
dateparser = "^1.2.2"
|
||||
decorator = "5.1.1"
|
||||
dnspython = "^2.8.0"
|
||||
docutils = "0.20.1"
|
||||
dropbox = "^12.0.2"
|
||||
easypost = "7.15.0"
|
||||
email-validator = "^2.3.0"
|
||||
extendable = ">=0.0.4"
|
||||
extendable-pydantic = ">=1.2.0"
|
||||
extract-msg = "^0.55.0"
|
||||
extruct = "^0.18.0"
|
||||
ezdxf = "^1.4.3"
|
||||
factur-x = "^3.14"
|
||||
factur-x = "^3.15"
|
||||
fastapi = ">=0.110.0"
|
||||
filetype = "^1.2.0"
|
||||
flake8 = "^7.3.0"
|
||||
formio-data = "^2.1.6"
|
||||
|
|
@ -54,7 +65,8 @@ geocoder = "^1.38.1"
|
|||
geoip2 = "2.9.0"
|
||||
geojson = "^3.2.0"
|
||||
gevent = "24.2.1"
|
||||
gitpython = "^3.1.45"
|
||||
gitpython = "^3.1.46"
|
||||
giturlparse = "0.12.0"
|
||||
googletrans-py = "4.0.0"
|
||||
greenlet = "3.0.3"
|
||||
httpagentparser = "^1.9.5"
|
||||
|
|
@ -72,7 +84,7 @@ mako = "^1.3.10"
|
|||
markdown2 = "2.5.1"
|
||||
markdownify = "0.13.1"
|
||||
markupsafe = "2.1.5"
|
||||
matplotlib = "^3.10.7"
|
||||
matplotlib = "^3.10.8"
|
||||
meteostat = "1.6.8"
|
||||
mock = "^5.2.0"
|
||||
mpld3 = "0.5.10"
|
||||
|
|
@ -83,43 +95,53 @@ num2words = "0.5.13"
|
|||
numpy = ">=1.15"
|
||||
numpy-financial = "<=1.0.0"
|
||||
numpy-stl = "^3.2.0"
|
||||
oauthlib = "^3.3.1"
|
||||
odoorpc = "^0.10.1"
|
||||
ofxparse = "0.21"
|
||||
openai = "^2.8.1"
|
||||
ollama = "^0.6.1"
|
||||
openai = "^2.15.0"
|
||||
openpyxl = "3.1.2"
|
||||
openupgradelib = "^3.12.0"
|
||||
openwebui-client = ">=0.3.0"
|
||||
ovh = "^1.2.0"
|
||||
pandas = ">=0.25.3"
|
||||
paramiko = "<4.0.0"
|
||||
parse-accept-language = "^0.1.2"
|
||||
passlib = "1.7.4"
|
||||
pathspec = "^0.12.1"
|
||||
pathspec = "^1.0.3"
|
||||
pdf2image = "^1.17.0"
|
||||
pdfminer = "^20191125"
|
||||
pexpect = "^4.9.0"
|
||||
phonenumbers = "^9.0.19"
|
||||
phonenumbers = "^9.0.22"
|
||||
pillow = "12.0.0"
|
||||
plotly = "^6.5.0"
|
||||
plaid-python = "^38.0.0"
|
||||
plotly = "^6.5.2"
|
||||
polib = "1.1.1"
|
||||
pre-commit = "^4.5.0"
|
||||
pre-commit = "^4.5.1"
|
||||
premailer = "^3.10.0"
|
||||
prometheus-client = "^0.23.1"
|
||||
prometheus-client = "^0.24.1"
|
||||
psutil = "5.9.8"
|
||||
psycopg2 = "2.9.9"
|
||||
py3dns = "^4.0.2"
|
||||
py3o-formats = "^0.3"
|
||||
py3o-template = "^0.10.0"
|
||||
pycountry = "^24.6.1"
|
||||
pycups = "^2.0.4"
|
||||
pydantic = ">=2.0.0"
|
||||
pydevd-odoo = "^1.2.2"
|
||||
pyftpdlib = "^2.1.0"
|
||||
pygithub = "^2.8.1"
|
||||
pyjsparser = "^2.7.1"
|
||||
pyjwt = "^2.10.1"
|
||||
pylint = "3.3.9"
|
||||
pylint-odoo = "^9.3.22"
|
||||
pylint = "^4.0.4"
|
||||
pylint-odoo = "^10.0.0"
|
||||
pymupdf = "^1.26.7"
|
||||
pyncclient = "^0.7"
|
||||
pyopenssl = "25.3.0"
|
||||
pypdf = ">=3.1.0"
|
||||
pypdf2 = "2.12.1"
|
||||
pyproj = "^3.7.2"
|
||||
pyquerystring = "^1.1"
|
||||
pyserial = "3.5"
|
||||
pysftp = "^0.2.9"
|
||||
pytesseract = "^0.3.13"
|
||||
|
|
@ -129,11 +151,12 @@ python-dotenv = ">=1.0.0"
|
|||
python-jose = "^3.5.0"
|
||||
python-json-logger = "^4.0.0"
|
||||
python-ldap = "3.4.4"
|
||||
python-multipart = "^0.0.21"
|
||||
python-pptx = "^1.0.2"
|
||||
python-resize-image = "^1.1.20"
|
||||
python-slugify = "^8.0.4"
|
||||
python-stdnum = "1.19"
|
||||
python-telegram-bot = "^22.5"
|
||||
python-telegram-bot = "^22.6"
|
||||
pytz = "^2025.2"
|
||||
pyusb = "1.2.1"
|
||||
pyyaml = "^6.0.3"
|
||||
|
|
@ -143,24 +166,30 @@ qifparse = "^0.5"
|
|||
qrcode = "7.4.2"
|
||||
radon = "^6.0.1"
|
||||
redis = "^7.1.0"
|
||||
regex = "^2026.1.15"
|
||||
reportlab = "4.1.0"
|
||||
requests = ">=2.31.0"
|
||||
requests-oauthlib = "^2.0.0"
|
||||
requests-toolbelt = "^1.0.0"
|
||||
responses = "^0.25.8"
|
||||
rjsmin = "1.2.0"
|
||||
roulier = "^1.1.1"
|
||||
schwifty = "2024.4.0"
|
||||
sentry-sdk = ">=2.0.0,<=2.22.0"
|
||||
setuptools = "80.9.0"
|
||||
shapely = "^2.1.2"
|
||||
sqlalchemy = "^2.0.44"
|
||||
sqlalchemy = "^2.0.46"
|
||||
statsd = "^4.0.1"
|
||||
tldextract = "^5.3.0"
|
||||
tldextract = "^5.3.1"
|
||||
tweepy = "^4.16.0"
|
||||
twilio = "^9.8.7"
|
||||
twilio = "^9.10.0"
|
||||
typing-extensions = "^4.15.0"
|
||||
ujson = "^5.11.0"
|
||||
unidecode = "^1.4.0"
|
||||
urllib3 = "2.0.7"
|
||||
uvloop = "^0.22.1"
|
||||
validate-email = "^1.3"
|
||||
validator-collection = "^1.5.0"
|
||||
vobject = "0.9.6.1"
|
||||
w3lib = "^2.3.1"
|
||||
werkzeug = "3.0.1"
|
||||
|
|
@ -177,7 +206,7 @@ rev = "24.8.0"
|
|||
|
||||
[tool.poetry.dependencies.fsspec]
|
||||
extras = [ "s3",]
|
||||
version = "^2025.10.0"
|
||||
version = "^2026.1.0"
|
||||
|
||||
[tool.poetry.dependencies.pysaml2]
|
||||
git = "https://github.com/prauscher/pysaml2.git"
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ meteostat==1.6.8
|
|||
beautifulsoup4==4.13.5
|
||||
# pylint-odoo depend on pylint==3.3.9 https://github.com/OCA/pylint-odoo/pull/541
|
||||
#pylint-odoo==9.3.22
|
||||
pylint==3.3.9
|
||||
#pylint==3.3.9
|
||||
isort==6.1.0
|
||||
|
||||
# Fix some OS
|
||||
|
|
|
|||
31
script/addons/install_addons_debug.sh
Executable file
31
script/addons/install_addons_debug.sh
Executable file
|
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/env bash
|
||||
Red='\033[0;31m' # Red
|
||||
Color_Off='\033[0m' # Text Reset
|
||||
|
||||
# Argument 3 is config
|
||||
if [[ $# -eq 3 ]]; then
|
||||
./script/addons/check_addons_exist.py -m "$2" -c "$3"
|
||||
else
|
||||
./script/addons/check_addons_exist.py -m "$2"
|
||||
fi
|
||||
retVal=$?
|
||||
if [[ $retVal -ne 0 ]]; then
|
||||
echo -e "${Red}Error${Color_Off} check_addons_exist.py into install_addons_debug.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Install module '$2' on BD '$1'"
|
||||
|
||||
# --log-level=debug for less debug information
|
||||
if [[ $# -eq 3 ]]; then
|
||||
./run.sh --no-http --stop-after-init --limit-memory-soft=8589934592 --limit-memory-hard=0 --dev cg -d "$1" -i "$2" -u "$2" -c "$3" --log-level=debug_sql --log-handler=odoo.modules.loading:DEBUG
|
||||
# ./run.sh --no-http --stop-after-init fd --limit-memory-soft=2048*1024*1024 --dev cg -d "$1" -i "$2" -u "$2" -c "$3"
|
||||
else
|
||||
./run.sh --no-http --stop-after-init --limit-memory-soft=8589934592 --limit-memory-hard=0 --dev cg -d "$1" -i "$2" -u "$2" --log-level=debug_sql --log-handler=odoo.modules.loading:DEBUG
|
||||
fi
|
||||
|
||||
retVal=$?
|
||||
if [[ $retVal -ne 0 ]]; then
|
||||
echo -e "${Red}Error${Color_Off} run.sh into install_addons_debug.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -173,7 +173,7 @@ def fill_search_field(ast_obj, var_name="", py_filename=""):
|
|||
elif ast_obj_type is ast.UnaryOp:
|
||||
if type(ast_obj.op) is ast.USub:
|
||||
# value is negative
|
||||
result = ast_obj.operand.n * -1
|
||||
result = ast_obj.operand.value * -1
|
||||
else:
|
||||
_logger.warning(
|
||||
f"Cannot support keyword of variable {var_name} type"
|
||||
|
|
@ -186,7 +186,7 @@ def fill_search_field(ast_obj, var_name="", py_filename=""):
|
|||
# Support -> fields.Date.context_today
|
||||
parent_node = ast_obj
|
||||
lst_call_lambda = []
|
||||
if hasattr(parent_node, "id"):
|
||||
if hasattr(parent_node, "id") or hasattr(parent_node, "attr"):
|
||||
while hasattr(parent_node, "value"):
|
||||
lst_call_lambda.insert(0, parent_node.attr)
|
||||
parent_node = parent_node.value
|
||||
|
|
@ -200,16 +200,28 @@ def fill_search_field(ast_obj, var_name="", py_filename=""):
|
|||
" parent_node is type ast.Call."
|
||||
)
|
||||
elif ast_obj_type is ast.List:
|
||||
result = [fill_search_field(a, var_name) for a in ast_obj.elts]
|
||||
result = [
|
||||
fill_search_field(a, var_name=var_name, py_filename=py_filename)
|
||||
for a in ast_obj.elts
|
||||
]
|
||||
elif ast_obj_type is ast.Dict:
|
||||
result = {
|
||||
fill_search_field(k, var_name): fill_search_field(
|
||||
ast_obj.values[i], var_name
|
||||
fill_search_field(
|
||||
k, var_name=var_name, py_filename=py_filename
|
||||
): fill_search_field(
|
||||
ast_obj.values[i], var_name=var_name, py_filename=py_filename
|
||||
)
|
||||
for (i, k) in enumerate(ast_obj.keys)
|
||||
}
|
||||
elif ast_obj_type is ast.Tuple:
|
||||
result = tuple([fill_search_field(a, var_name) for a in ast_obj.elts])
|
||||
result = tuple(
|
||||
[
|
||||
fill_search_field(
|
||||
a, var_name=var_name, py_filename=py_filename
|
||||
)
|
||||
for a in ast_obj.elts
|
||||
]
|
||||
)
|
||||
else:
|
||||
_logger.warning(
|
||||
f"Cannot support keyword of variable {var_name} type"
|
||||
|
|
@ -321,8 +333,10 @@ def main():
|
|||
dct_fields[var_name] = d
|
||||
lst_args = []
|
||||
for keyword in node.value.args:
|
||||
value = _fill_search_field(
|
||||
keyword, var_name
|
||||
value = fill_search_field(
|
||||
keyword,
|
||||
var_name=var_name,
|
||||
py_filename=str(py_file),
|
||||
)
|
||||
# Waste to stock None value
|
||||
if value is not None:
|
||||
|
|
@ -350,7 +364,9 @@ def main():
|
|||
)
|
||||
for keyword in node.value.keywords:
|
||||
value = fill_search_field(
|
||||
keyword.value, var_name
|
||||
keyword.value,
|
||||
var_name=var_name,
|
||||
py_filename=str(py_file),
|
||||
)
|
||||
if value is not None:
|
||||
d[keyword.arg] = value
|
||||
|
|
@ -411,67 +427,6 @@ def main():
|
|||
return 0
|
||||
|
||||
|
||||
def extract_lambda(self, node):
|
||||
result = ast.unparse(node)
|
||||
if result[0] == "(" and result[-1] == ")":
|
||||
result = result[1:-1]
|
||||
return result
|
||||
|
||||
|
||||
def _fill_search_field(ast_obj, var_name=""):
|
||||
ast_obj_type = type(ast_obj)
|
||||
result = None
|
||||
if ast_obj_type is ast.Lambda:
|
||||
result = extract_lambda(ast_obj)
|
||||
elif ast_obj_type is ast.Constant:
|
||||
result = ast_obj.value
|
||||
elif ast_obj_type is ast.UnaryOp:
|
||||
if type(ast_obj.op) is ast.USub:
|
||||
# value is negative
|
||||
result = ast_obj.operand.n * -1
|
||||
else:
|
||||
_logger.warning(
|
||||
f"Cannot support keyword of variable {var_name} type"
|
||||
f" {ast_obj_type} operator {type(ast_obj.op)}."
|
||||
)
|
||||
elif ast_obj_type is ast.Name:
|
||||
result = ast_obj.id
|
||||
elif ast_obj_type is ast.Attribute:
|
||||
# Support -> fields.Date.context_today
|
||||
parent_node = ast_obj
|
||||
lst_call_lambda = []
|
||||
if hasattr(parent_node, "id"):
|
||||
while hasattr(parent_node, "value"):
|
||||
lst_call_lambda.insert(0, parent_node.attr)
|
||||
parent_node = parent_node.value
|
||||
lst_call_lambda.insert(0, parent_node.id)
|
||||
result = ".".join(lst_call_lambda)
|
||||
# else:
|
||||
# # default=uuid.uuid4().hex
|
||||
# _logger.warning(
|
||||
# f"Cannot support keyword of variable {var_name} type"
|
||||
# f" {ast_obj_type} in filename {self.py_filename}, because"
|
||||
# " parent_node is type ast.Call."
|
||||
# )
|
||||
elif ast_obj_type is ast.List:
|
||||
result = [_fill_search_field(a, var_name) for a in ast_obj.elts]
|
||||
elif ast_obj_type is ast.Dict:
|
||||
result = {
|
||||
_fill_search_field(k, var_name): _fill_search_field(
|
||||
ast_obj.values[i], var_name
|
||||
)
|
||||
for (i, k) in enumerate(ast_obj.keys)
|
||||
}
|
||||
elif ast_obj_type is ast.Tuple:
|
||||
result = tuple([_fill_search_field(a, var_name) for a in ast_obj.elts])
|
||||
else:
|
||||
_logger.warning(
|
||||
f"Cannot support keyword of variable {var_name} type"
|
||||
f" {ast_obj_type}."
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
status = main()
|
||||
sys.exit(status)
|
||||
|
|
|
|||
97
script/config/setup_odoo_config_conf_devops.py
Executable file
97
script/config/setup_odoo_config_conf_devops.py
Executable file
|
|
@ -0,0 +1,97 @@
|
|||
#!/usr/bin/env python3
|
||||
# © 2021-2025 TechnoLibre (http://www.technolibre.ca)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
import argparse
|
||||
import configparser
|
||||
import getpass
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from subprocess import check_output
|
||||
|
||||
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_config():
|
||||
"""Parse command line arguments, extracting the config file name,
|
||||
returning the union of config file and command line arguments
|
||||
|
||||
:return: dict of config file settings and command line arguments
|
||||
"""
|
||||
# TODO update description
|
||||
parser = argparse.ArgumentParser(
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
description="""\
|
||||
DESCRIPTION
|
||||
Force config.conf file to has workers 0 and support queue
|
||||
|
||||
SUGGESTION
|
||||
./script/config/setup_odoo_config_conf_devops.py
|
||||
""",
|
||||
epilog="""\
|
||||
""",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
|
||||
def main():
|
||||
config = get_config()
|
||||
|
||||
# check if it needs master password from config file
|
||||
has_config_file = True
|
||||
config_path = "./config.conf"
|
||||
if not os.path.isfile(config_path):
|
||||
config_path = "/etc/odoo/odoo.conf"
|
||||
if not os.path.isfile(config_path):
|
||||
has_config_file = False
|
||||
if has_config_file:
|
||||
config_parser = configparser.ConfigParser()
|
||||
config_parser.read(config_path)
|
||||
|
||||
# Force workers to zero for local running
|
||||
if config_parser.has_option("options", "workers"):
|
||||
workers = config_parser.get("options", "workers")
|
||||
if workers != "0":
|
||||
config_parser.set("options", "workers", "0")
|
||||
else:
|
||||
config_parser.set("options", "workers", "0")
|
||||
|
||||
# Load queue_job
|
||||
if config_parser.has_option("options", "server_wide_modules"):
|
||||
server_wide_modules = config_parser.get(
|
||||
"options", "server_wide_modules"
|
||||
)
|
||||
if "queue_job" not in server_wide_modules:
|
||||
config_parser.set(
|
||||
"options",
|
||||
"server_wide_modules",
|
||||
server_wide_modules + ",queue_job",
|
||||
)
|
||||
else:
|
||||
config_parser.set(
|
||||
"options", "server_wide_modules", "base,web,queue_job"
|
||||
)
|
||||
|
||||
# Support queue_job size channels
|
||||
queue_job_channels = ""
|
||||
if config_parser.has_section("queue_job"):
|
||||
if config_parser.has_option("options", "queue_job"):
|
||||
queue_job_channels = config_parser.get("queue_job", "channels")
|
||||
else:
|
||||
config_parser.add_section("queue_job")
|
||||
|
||||
if not queue_job_channels:
|
||||
config_parser.set(
|
||||
"queue_job", "channels", f"root:{os.cpu_count()}"
|
||||
)
|
||||
|
||||
with open(config_path, "w") as configfile:
|
||||
config_parser.write(configfile)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -42,7 +42,10 @@ if [[ ${EL_MINIMAL_ADDONS} = "False" ]]; then
|
|||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_account-invoicing," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_account-payment," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_account-reconcile," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_agreement," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_ai," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_apps-store," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_automation," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_bank-payment," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_bank-payment-alternative," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_bank-statement-import," >> "${EL_CONFIG_FILE}"
|
||||
|
|
@ -74,19 +77,28 @@ if [[ ${EL_MINIMAL_ADDONS} = "False" ]]; then
|
|||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_geospatial," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_helpdesk," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_hr," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_hr-attendance," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_hr-expense," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_hr-holidays," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_infrastructure," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_interface-github," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_intrastat-extrastat," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_iot," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_knowledge," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_l10n-canada," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_mail," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_maintenance," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_management-system," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_manufacture," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_manufacture-reporting," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_margin-analysis," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_mass-mailing," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_mis-builder," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_multi-company," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_odoo-pim," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_operating-unit," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_partner-contact," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_payroll," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_pos," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_product-attribute," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_product-configurator," >> "${EL_CONFIG_FILE}"
|
||||
|
|
@ -95,10 +107,17 @@ if [[ ${EL_MINIMAL_ADDONS} = "False" ]]; then
|
|||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_project," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_project-agile," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_project-reporting," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_purchase-reporting," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_purchase-workflow," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_queue," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_repair," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_report-print-send," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_reporting-engine," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_rest-framework," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_rma," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_sale-blanket," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_sale-channel," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_sale-promotion," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_sale-reporting," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_sale-workflow," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_server-auth," >> "${EL_CONFIG_FILE}"
|
||||
|
|
@ -107,6 +126,8 @@ if [[ ${EL_MINIMAL_ADDONS} = "False" ]]; then
|
|||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_server-env," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_server-tools," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_server-ux," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_shift-planning," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_shopfloor-app," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_sign," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_social," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_spreadsheet," >> "${EL_CONFIG_FILE}"
|
||||
|
|
@ -120,17 +141,21 @@ if [[ ${EL_MINIMAL_ADDONS} = "False" ]]; then
|
|||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_vertical-isp," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_vertical-travel," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_web," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_web-api," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_website," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_website-cms," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OCA_wms," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/ONLYOFFICE_onlyoffice_odoo," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/OmniaGit_odooplm," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/TechnoLibre_odoo-code-generator," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/TechnoLibre_odoo-code-generator-template," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/bemade_bemade-addons," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/camptocamp_odoo-cloud-platform," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/cetmix_cetmix-tower," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/dhongu_deltatech," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/muk-it_odoo-modules," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/novacode-nl_odoo-formio," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/odoo_design-themes," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/odoomates_odooapps," >> "${EL_CONFIG_FILE}"
|
||||
printf "${EL_HOME_ODOO_PROJECT}/addons/openeducat_openeducat_erp," >> "${EL_CONFIG_FILE}"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -14,6 +14,6 @@ sudo snap install core
|
|||
sudo snap refresh core
|
||||
|
||||
# https://certbot.eff.org/lets-encrypt/debianbuster-nginx
|
||||
# Cerbot
|
||||
# Certbot
|
||||
sudo snap install --classic certbot
|
||||
sudo ln -s /snap/bin/certbot /usr/bin/certbot
|
||||
|
|
|
|||
|
|
@ -14,6 +14,6 @@ sudo snap install core
|
|||
sudo snap refresh core
|
||||
|
||||
# https://certbot.eff.org/lets-encrypt/debianbuster-nginx
|
||||
# Cerbot
|
||||
# Certbot
|
||||
sudo snap install --classic certbot
|
||||
sudo ln -s /snap/bin/certbot /usr/bin/certbot
|
||||
|
|
|
|||
193
script/process/kill_process_by_port.py
Executable file
193
script/process/kill_process_by_port.py
Executable file
|
|
@ -0,0 +1,193 @@
|
|||
#!/usr/bin/env python3
|
||||
# © 2021-2025 TechnoLibre (http://www.technolibre.ca)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
import psutil
|
||||
|
||||
STOP_PARENT_KILL = ["./odoo_bin.sh", "./run.sh"]
|
||||
|
||||
|
||||
def proc_desc(p: psutil.Process) -> str:
|
||||
try:
|
||||
cmd = " ".join(p.cmdline()) if p.cmdline() else p.name()
|
||||
return f"pid={p.pid} user={p.username()} name={p.name()} cmd={cmd}"
|
||||
except psutil.Error:
|
||||
return f"pid={p.pid}"
|
||||
|
||||
|
||||
def get_ancestry(pid: int):
|
||||
"""
|
||||
Returns list of psutil.Process from child -> parent -> ... until PID 1 or missing.
|
||||
"""
|
||||
chain = []
|
||||
try:
|
||||
p = psutil.Process(pid)
|
||||
except psutil.NoSuchProcess:
|
||||
return chain
|
||||
|
||||
while True:
|
||||
chain.append(p)
|
||||
try:
|
||||
ppid = p.ppid()
|
||||
except psutil.Error:
|
||||
break
|
||||
if ppid <= 0 or ppid == p.pid:
|
||||
break
|
||||
try:
|
||||
p = psutil.Process(ppid)
|
||||
except psutil.NoSuchProcess:
|
||||
break
|
||||
if p.pid == 1:
|
||||
chain.append(p)
|
||||
break
|
||||
return chain
|
||||
|
||||
|
||||
def choose_target(chain, nb_parent):
|
||||
"""
|
||||
Decide which process to kill.
|
||||
Default: kill the highest ancestor before PID 1 (so not systemd).
|
||||
"""
|
||||
if not chain:
|
||||
return None
|
||||
|
||||
lst_chain_parent = []
|
||||
for pid in chain:
|
||||
lst_chain_parent.append(pid)
|
||||
for str_to_stop in STOP_PARENT_KILL:
|
||||
if str_to_stop in pid.cmdline():
|
||||
return pid, lst_chain_parent
|
||||
return chain[nb_parent - 1], [chain[nb_parent - 1]]
|
||||
|
||||
|
||||
def kill_process(p: psutil.Process, force: bool):
|
||||
if force:
|
||||
p.kill()
|
||||
else:
|
||||
p.terminate()
|
||||
|
||||
|
||||
def kill_tree(root: psutil.Process, force: bool):
|
||||
"""
|
||||
Kill root and all its children (best-effort).
|
||||
"""
|
||||
children = root.children(recursive=True)
|
||||
# terminate children first
|
||||
for ch in children:
|
||||
try:
|
||||
kill_process(ch, force=force)
|
||||
except psutil.Error:
|
||||
pass
|
||||
try:
|
||||
kill_process(root, force=force)
|
||||
except psutil.Error:
|
||||
pass
|
||||
|
||||
# wait a bit
|
||||
gone, alive = psutil.wait_procs(children + [root], timeout=3)
|
||||
return alive
|
||||
|
||||
|
||||
def find_listeners(port: int):
|
||||
pids = set()
|
||||
for c in psutil.net_connections(kind="tcp"):
|
||||
if (
|
||||
c.laddr
|
||||
and c.laddr.port == port
|
||||
and c.status == psutil.CONN_LISTEN
|
||||
and c.pid
|
||||
):
|
||||
pids.add(c.pid)
|
||||
return sorted(pids)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(
|
||||
description="Free a TCP port by killing the parent (or process tree) of the listener."
|
||||
)
|
||||
ap.add_argument("port", type=int, help="TCP port (1-65535)")
|
||||
ap.add_argument(
|
||||
"-n",
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Show what would be killed, do nothing",
|
||||
)
|
||||
ap.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
action="store_true",
|
||||
help="Use SIGKILL instead of SIGTERM",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--kill-tree",
|
||||
action="store_true",
|
||||
help="Kill target and all its children recursively",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--nb_parent",
|
||||
type=int,
|
||||
default=1,
|
||||
help="Kill parent too",
|
||||
)
|
||||
args = ap.parse_args()
|
||||
|
||||
if not (1 <= args.port <= 65535):
|
||||
print("Port invalide (1-65535).", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
pids = find_listeners(args.port)
|
||||
if not pids:
|
||||
print(f"Aucun process en LISTEN sur {args.port}.")
|
||||
return 1
|
||||
|
||||
for pid in pids:
|
||||
chain = get_ancestry(pid)
|
||||
if not chain:
|
||||
continue
|
||||
|
||||
target, lst_target = choose_target(chain, args.nb_parent)
|
||||
|
||||
print(f"\nListener PID {pid} ancestry:")
|
||||
for i, p in enumerate(chain):
|
||||
prefix = " " + ("-> " if p == target else " ")
|
||||
print(prefix + proc_desc(p))
|
||||
|
||||
if target is None:
|
||||
continue
|
||||
|
||||
if target.pid == 1 and not args.allow_pid1:
|
||||
print(
|
||||
"Refus: la cible est PID 1 (systemd). Utilise plutôt systemctl pour arrêter le service.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
continue
|
||||
|
||||
action = "KILL" if args.force else "TERM"
|
||||
if args.kill_tree:
|
||||
print(f"Target: pid={target.pid} ({action}) + children (tree)")
|
||||
if not args.dry_run:
|
||||
alive = kill_tree(target, force=args.force)
|
||||
if alive:
|
||||
print(
|
||||
"Toujours vivants:",
|
||||
", ".join(str(p.pid) for p in alive),
|
||||
file=sys.stderr,
|
||||
)
|
||||
else:
|
||||
print(f"Target: pid={target.pid} ({action})")
|
||||
if not args.dry_run:
|
||||
try:
|
||||
kill_process(target, force=args.force)
|
||||
except psutil.AccessDenied as e:
|
||||
print(
|
||||
f"AccessDenied: {e} (lance le script avec sudo).",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -68,6 +68,8 @@ MONTHS_FR = {
|
|||
"decembre": 12,
|
||||
}
|
||||
|
||||
defaut_timeout = 60
|
||||
|
||||
|
||||
class SeleniumLib(object):
|
||||
def __init__(self, config):
|
||||
|
|
@ -437,7 +439,7 @@ class SeleniumLib(object):
|
|||
word = ""
|
||||
return word
|
||||
|
||||
def click(self, xpath, timeout=5):
|
||||
def click(self, xpath, timeout=defaut_timeout):
|
||||
wait = WebDriverWait(self.driver, timeout)
|
||||
button = wait.until(
|
||||
EC.element_to_be_clickable(
|
||||
|
|
@ -454,7 +456,7 @@ class SeleniumLib(object):
|
|||
self,
|
||||
by: str = By.ID,
|
||||
value: str = None,
|
||||
timeout=5,
|
||||
timeout=defaut_timeout,
|
||||
wait_clickable=False,
|
||||
wait_is_invisible=False,
|
||||
is_visible=True,
|
||||
|
|
@ -503,7 +505,7 @@ class SeleniumLib(object):
|
|||
return ele[0]
|
||||
|
||||
def get_element_not_visible(
|
||||
self, by: str = By.ID, value: str = None, timeout=5
|
||||
self, by: str = By.ID, value: str = None, timeout=defaut_timeout
|
||||
):
|
||||
wait = WebDriverWait(self.driver, timeout)
|
||||
ele = wait.until(
|
||||
|
|
@ -518,7 +520,7 @@ class SeleniumLib(object):
|
|||
return ele
|
||||
|
||||
def get_elements_not_visible(
|
||||
self, by: str = By.ID, value: str = None, timeout=5
|
||||
self, by: str = By.ID, value: str = None, timeout=defaut_timeout
|
||||
):
|
||||
wait = WebDriverWait(self.driver, timeout)
|
||||
ele = wait.until(
|
||||
|
|
@ -533,12 +535,14 @@ class SeleniumLib(object):
|
|||
return ele
|
||||
|
||||
def get_text_from_element(
|
||||
self, by: str = By.ID, value: str = None, timeout=5
|
||||
self, by: str = By.ID, value: str = None, timeout=defaut_timeout
|
||||
):
|
||||
ele = self.get_element(by, value, timeout=timeout)
|
||||
return ele.text
|
||||
|
||||
def get_all_element(self, by: str = By.ID, value: str = None, timeout=5):
|
||||
def get_all_element(
|
||||
self, by: str = By.ID, value: str = None, timeout=defaut_timeout
|
||||
):
|
||||
wait = WebDriverWait(self.driver, timeout)
|
||||
ele = wait.until(
|
||||
EC.visibility_of_all_elements_located(
|
||||
|
|
@ -556,7 +560,7 @@ class SeleniumLib(object):
|
|||
by: str = By.ID,
|
||||
value: str = None,
|
||||
delay_wait=1,
|
||||
timeout=5,
|
||||
timeout=defaut_timeout,
|
||||
inject_cursor=False,
|
||||
):
|
||||
new_element = self.wait_new_element(
|
||||
|
|
@ -573,7 +577,7 @@ class SeleniumLib(object):
|
|||
by: str = By.ID,
|
||||
value: str = None,
|
||||
delay_wait=1,
|
||||
timeout=5,
|
||||
timeout=defaut_timeout,
|
||||
timeout_wait=None,
|
||||
):
|
||||
element = None
|
||||
|
|
@ -588,7 +592,11 @@ class SeleniumLib(object):
|
|||
return element
|
||||
|
||||
def wait_add_new_element_and_click(
|
||||
self, by: str = By.ID, value: str = None, delay_wait=1, timeout=5
|
||||
self,
|
||||
by: str = By.ID,
|
||||
value: str = None,
|
||||
delay_wait=1,
|
||||
timeout=defaut_timeout,
|
||||
):
|
||||
new_element = self.wait_add_new_element(
|
||||
by=by, value=value, delay_wait=delay_wait, timeout=timeout
|
||||
|
|
@ -597,7 +605,11 @@ class SeleniumLib(object):
|
|||
return new_element
|
||||
|
||||
def wait_add_new_element(
|
||||
self, by: str = By.ID, value: str = None, delay_wait=1, timeout=5
|
||||
self,
|
||||
by: str = By.ID,
|
||||
value: str = None,
|
||||
delay_wait=1,
|
||||
timeout=defaut_timeout,
|
||||
):
|
||||
all_element_init = self.get_all_element(by=by, value=value)
|
||||
len_all_element_init = len(all_element_init)
|
||||
|
|
@ -612,7 +624,11 @@ class SeleniumLib(object):
|
|||
return new_element
|
||||
|
||||
def wait_increment_number_text_and_click(
|
||||
self, by: str = By.ID, value: str = None, delay_wait=1, timeout=5
|
||||
self,
|
||||
by: str = By.ID,
|
||||
value: str = None,
|
||||
delay_wait=1,
|
||||
timeout=defaut_timeout,
|
||||
):
|
||||
element_init = self.get_element(by=by, value=value, timeout=timeout)
|
||||
actual_number = int(element_init.text)
|
||||
|
|
@ -631,7 +647,7 @@ class SeleniumLib(object):
|
|||
self,
|
||||
by: str = By.ID,
|
||||
value: str = None,
|
||||
timeout: int = 5,
|
||||
timeout: int = defaut_timeout,
|
||||
no_scroll: bool = False,
|
||||
viewport_ele_by: str = By.ID,
|
||||
viewport_ele_value: str = None,
|
||||
|
|
@ -696,7 +712,7 @@ class SeleniumLib(object):
|
|||
self,
|
||||
by: str = By.ID,
|
||||
value: str = None,
|
||||
timeout: int = 5,
|
||||
timeout: int = defaut_timeout,
|
||||
form="carre",
|
||||
):
|
||||
|
||||
|
|
@ -720,7 +736,7 @@ class SeleniumLib(object):
|
|||
value: str = None,
|
||||
text_value: str = "",
|
||||
delay_after: int = -1,
|
||||
timeout: int = 5,
|
||||
timeout: int = defaut_timeout,
|
||||
no_scroll: bool = False,
|
||||
viewport_ele_by: str = By.ID,
|
||||
viewport_ele_value: str = None,
|
||||
|
|
@ -1093,7 +1109,7 @@ class SeleniumLib(object):
|
|||
" odoo_website_menu_click"
|
||||
)
|
||||
button = self.click_with_mouse_move(
|
||||
By.LINK_TEXT, from_text, timeout=30
|
||||
By.LINK_TEXT, from_text, timeout=defaut_timeout
|
||||
)
|
||||
return button
|
||||
|
||||
|
|
@ -1113,11 +1129,10 @@ class SeleniumLib(object):
|
|||
By.XPATH,
|
||||
"//button[contains(@class, 'dropdown-toggle') and"
|
||||
" .//i[contains(@class, 'oi-apps')]]",
|
||||
timeout=10,
|
||||
)
|
||||
elif self.odoo_version == "14.0":
|
||||
button = self.click_with_mouse_move(
|
||||
By.CSS_SELECTOR, "a.full[data-toggle='dropdown']", timeout=30
|
||||
By.CSS_SELECTOR, "a.full[data-toggle='dropdown']"
|
||||
)
|
||||
return button
|
||||
|
||||
|
|
@ -1126,7 +1141,6 @@ class SeleniumLib(object):
|
|||
all_row = self.get_all_element(
|
||||
by=By.XPATH,
|
||||
value=f"//tr[contains(@class, 'o_data_row')]/td[contains(@name, '{field_name}')]",
|
||||
timeout=10,
|
||||
)
|
||||
row = all_row[index] if index < len(all_row) else all_row[-1]
|
||||
button = self.click_with_mouse_move(element=row)
|
||||
|
|
@ -1137,7 +1151,6 @@ class SeleniumLib(object):
|
|||
button = self.click_with_mouse_move(
|
||||
By.XPATH,
|
||||
"//button[contains(@class, 'o_searchview_dropdown_toggler')]",
|
||||
timeout=10,
|
||||
)
|
||||
return button
|
||||
|
||||
|
|
@ -1146,14 +1159,12 @@ class SeleniumLib(object):
|
|||
button = self.click_with_mouse_move(
|
||||
By.XPATH,
|
||||
value="//select[contains(@class, 'o_add_custom_group_menu')]",
|
||||
timeout=10,
|
||||
)
|
||||
if self.wait_human_time:
|
||||
time.sleep(1)
|
||||
select_elem = self.get_element(
|
||||
By.XPATH,
|
||||
value="//select[contains(@class, 'o_add_custom_group_menu')]",
|
||||
timeout=10,
|
||||
wait_clickable=False,
|
||||
)
|
||||
|
||||
|
|
@ -1174,20 +1185,17 @@ class SeleniumLib(object):
|
|||
button = self.click_with_mouse_move(
|
||||
By.XPATH,
|
||||
value=f"//button[contains(@class,'o_menu_item') and contains(normalize-space(.), '{filter_label}')]",
|
||||
timeout=10,
|
||||
)
|
||||
if filter_sub_label:
|
||||
button = self.click_with_mouse_move(
|
||||
By.XPATH,
|
||||
value=f"//span[contains(@class,'o_item_option') and contains(normalize-space(.), '{filter_sub_label}')]",
|
||||
timeout=10,
|
||||
)
|
||||
if lst_update_condition:
|
||||
filter_text = f"{filter_label}: {filter_sub_label}"
|
||||
button = self.click_with_mouse_move(
|
||||
By.XPATH,
|
||||
value=f"//small[contains(@class,'o_facet_value') and contains(normalize-space(.), '{filter_text}')]/../..//i[contains(@class,'fa-cog')]",
|
||||
timeout=10,
|
||||
is_visible=False,
|
||||
)
|
||||
time.sleep(1)
|
||||
|
|
@ -1197,7 +1205,6 @@ class SeleniumLib(object):
|
|||
select_elem = self.click_with_mouse_move(
|
||||
By.XPATH,
|
||||
value=f"//div[contains(@class,'modal-content')]//select[contains(@class,'pe-3')]",
|
||||
timeout=10,
|
||||
is_visible=False,
|
||||
)
|
||||
select_obj = Select(select_elem)
|
||||
|
|
@ -1211,14 +1218,12 @@ class SeleniumLib(object):
|
|||
button = self.click_with_mouse_move(
|
||||
By.XPATH,
|
||||
value=f"//footer[contains(@class,'modal-footer')]//button[contains(@class,'btn-primary') and contains(normalize-space(.), 'Confirmer')]",
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
def odoo_fill_search_input(self, text, selection_research=None):
|
||||
input_elem = self.click_with_mouse_move(
|
||||
By.XPATH,
|
||||
value="//input[contains(@class, 'o_searchview_input')]",
|
||||
timeout=10,
|
||||
)
|
||||
input_elem.send_keys(text)
|
||||
time.sleep(1)
|
||||
|
|
@ -1240,15 +1245,13 @@ class SeleniumLib(object):
|
|||
"Cannot click, empty parameter from method"
|
||||
" odoo_web_principal_menu_click_root_menu"
|
||||
)
|
||||
button = self.click_with_mouse_move(
|
||||
By.LINK_TEXT, from_text, timeout=30
|
||||
)
|
||||
button = self.click_with_mouse_move(By.LINK_TEXT, from_text)
|
||||
# If not work, force it
|
||||
# selenium_tool.driver.execute_script("arguments[0].click();", button)
|
||||
return button
|
||||
|
||||
def odoo_web_form_click_statusbar_button_status(
|
||||
self, status_label, timeout=10
|
||||
self, status_label, timeout=defaut_timeout
|
||||
):
|
||||
try:
|
||||
status_button = self.click_with_mouse_move(
|
||||
|
|
@ -1294,7 +1297,7 @@ class SeleniumLib(object):
|
|||
toggle_data_menu("color_scheme.switch", enable_dark_mode)
|
||||
|
||||
def odoo_web_form_click_statusbar_button_status_floating(
|
||||
self, status_label, timeout=10
|
||||
self, status_label, timeout=defaut_timeout
|
||||
):
|
||||
status_button = self.click_with_mouse_move(
|
||||
By.XPATH,
|
||||
|
|
@ -1306,7 +1309,7 @@ class SeleniumLib(object):
|
|||
return status_button
|
||||
|
||||
def odoo_web_form_click_statusbar_button_status_plus(
|
||||
self, status_label, timeout=10
|
||||
self, status_label, timeout=defaut_timeout
|
||||
):
|
||||
status_button = self.click_with_mouse_move(
|
||||
By.XPATH,
|
||||
|
|
@ -1324,7 +1327,6 @@ class SeleniumLib(object):
|
|||
By.XPATH,
|
||||
f"//button[contains(@class, '{btn_class}') and contains(span,"
|
||||
f" '{btn_label}')]",
|
||||
timeout=30,
|
||||
)
|
||||
print(f"Bouton du statusbar avec le label '{btn_label}' cliqué.")
|
||||
return status_button
|
||||
|
|
@ -1333,7 +1335,6 @@ class SeleniumLib(object):
|
|||
status_button = self.click_with_mouse_move(
|
||||
By.NAME,
|
||||
btn_action,
|
||||
timeout=30,
|
||||
)
|
||||
print(f"Bouton avec l'action '{btn_action}' cliqué.")
|
||||
return status_button
|
||||
|
|
@ -1343,9 +1344,7 @@ class SeleniumLib(object):
|
|||
"o_form_button_save"
|
||||
)
|
||||
|
||||
def odoo_web_change_view(
|
||||
self, view_name="list", raise_error=False, timeout=5
|
||||
):
|
||||
def odoo_web_change_view(self, view_name="list", raise_error=False):
|
||||
if view_name not in ["list", "kanban", "timeline"]:
|
||||
msg = f"odoo_web_change_view view_name '{view_name}' invalid"
|
||||
if raise_error:
|
||||
|
|
@ -1367,7 +1366,6 @@ class SeleniumLib(object):
|
|||
status_button = self.click_with_mouse_move(
|
||||
By.CLASS_NAME,
|
||||
custom_class_name,
|
||||
timeout=30,
|
||||
)
|
||||
print(f"Bouton avec l'action '{custom_class_name}' cliqué.")
|
||||
return status_button
|
||||
|
|
@ -1378,7 +1376,6 @@ class SeleniumLib(object):
|
|||
By.XPATH,
|
||||
"//div[contains(@class,"
|
||||
f" 'o_kanban_record')]//span[contains(text(), '{card_label}')]",
|
||||
timeout=30,
|
||||
)
|
||||
# kanban_card = self.driver.find_element(By.XPATH,
|
||||
# f"//div[contains(@class, 'o_kanban_record')]//span[contains(text(), '{card_label}')]")
|
||||
|
|
@ -1388,9 +1385,7 @@ class SeleniumLib(object):
|
|||
|
||||
def odoo_web_action_button_new(self):
|
||||
# From web view kanban, list
|
||||
return self.click_with_mouse_move(
|
||||
By.CLASS_NAME, "o-kanban-button-new", timeout=30
|
||||
)
|
||||
return self.click_with_mouse_move(By.CLASS_NAME, "o-kanban-button-new")
|
||||
|
||||
def odoo_web_kanban_click(self, button_text, btn_class="btn-primary"):
|
||||
# From web view kanban, list
|
||||
|
|
@ -1398,14 +1393,12 @@ class SeleniumLib(object):
|
|||
By.XPATH,
|
||||
f"//button[contains(@class, 'btn {btn_class} o_kanban_edit') and"
|
||||
f" text()='{button_text}']",
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
def odoo_web_form_notebook_tab_click(self, tab_text):
|
||||
return self.click_with_mouse_move(
|
||||
By.XPATH,
|
||||
f"//a[text()='{tab_text}']",
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
# Web
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
# © 2021-2025 TechnoLibre (http://www.technolibre.ca)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
import configparser
|
||||
import datetime
|
||||
import getpass
|
||||
import json
|
||||
|
|
@ -11,6 +12,7 @@ import shutil
|
|||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import xml.etree.ElementTree as ET
|
||||
import zipfile
|
||||
|
||||
new_path = os.path.normpath(
|
||||
|
|
@ -29,6 +31,13 @@ INSTALLED_ODOO_VERSION_FILE = os.path.join(
|
|||
ODOO_VERSION_FILE = ".odoo-version"
|
||||
ENABLE_CRASH = False
|
||||
CRASH_E = None
|
||||
# Support mobile ERPLibre
|
||||
ANDROID_DIR = "android"
|
||||
MOBILE_HOME_PATH = "./mobile/erplibre_home_mobile"
|
||||
STRINGS_FILE = os.path.join(
|
||||
MOBILE_HOME_PATH, ANDROID_DIR, "app/src/main/res/values/strings.xml"
|
||||
)
|
||||
GRADLE_FILE = os.path.join(MOBILE_HOME_PATH, ANDROID_DIR, "app/build.gradle")
|
||||
|
||||
try:
|
||||
import tkinter as tk
|
||||
|
|
@ -66,7 +75,6 @@ _logger = logging.getLogger(__name__)
|
|||
CONFIG_FILE = "./script/todo/todo.json"
|
||||
CONFIG_OVERRIDE_FILE = "./private/todo/todo.json"
|
||||
LOGO_ASCII_FILE = "./script/todo/logo_ascii.txt"
|
||||
PATH_MOBILE_PROJECT_HOME = "./mobile/technolibre_home_mobile/technolibre_home"
|
||||
|
||||
|
||||
class TODO:
|
||||
|
|
@ -220,6 +228,7 @@ class TODO:
|
|||
[4] Code - Outil pour développeur
|
||||
[5] Doc - Recherche de documentation
|
||||
[6] Database - Outils sur les bases de données
|
||||
[7] Process - Outils sur les exécutions
|
||||
[0] Retour
|
||||
"""
|
||||
while True:
|
||||
|
|
@ -251,6 +260,10 @@ class TODO:
|
|||
status = self.prompt_execute_database()
|
||||
if status is not False:
|
||||
return
|
||||
elif status == "7":
|
||||
status = self.prompt_execute_process()
|
||||
if status is not False:
|
||||
return
|
||||
else:
|
||||
print("Commande non trouvée 🤖!")
|
||||
|
||||
|
|
@ -463,7 +476,7 @@ class TODO:
|
|||
lst_instance = self.config_file.get_config("instance")
|
||||
init_len = len(lst_instance)
|
||||
|
||||
if os.path.exists(PATH_MOBILE_PROJECT_HOME):
|
||||
if os.path.exists(MOBILE_HOME_PATH):
|
||||
dct_upgrade_odoo_database = {
|
||||
"prompt_description": "Mobile - Compile and run software",
|
||||
"callback": self.callback_make_mobile_home,
|
||||
|
|
@ -681,6 +694,23 @@ class TODO:
|
|||
else:
|
||||
print("Commande non trouvée 🤖!")
|
||||
|
||||
def prompt_execute_process(self):
|
||||
print("🤖 Manipuler les processus d'exécution!")
|
||||
lst_instance = [
|
||||
{"prompt_description": "Kill process from actual port"},
|
||||
]
|
||||
help_info = self.fill_help_info(lst_instance)
|
||||
|
||||
while True:
|
||||
status = click.prompt(help_info)
|
||||
print()
|
||||
if status == "0":
|
||||
return False
|
||||
elif status == "1":
|
||||
self.process_kill_from_port()
|
||||
else:
|
||||
print("Commande non trouvée 🤖!")
|
||||
|
||||
def get_odoo_version(self):
|
||||
with open(VERSION_DATA_FILE) as txt:
|
||||
data_version = json.load(txt)
|
||||
|
|
@ -826,7 +856,7 @@ class TODO:
|
|||
commande = (
|
||||
f"source ./.venv.{source_odoo}/bin/activate && {commande}"
|
||||
)
|
||||
elif new_window:
|
||||
elif new_window and self.cmd_source_default:
|
||||
commande = self.cmd_source_default % commande
|
||||
|
||||
print("🏠 ⬇ Execute command :")
|
||||
|
|
@ -1056,6 +1086,16 @@ class TODO:
|
|||
source_erplibre=False,
|
||||
)
|
||||
|
||||
def process_kill_from_port(self):
|
||||
cfg = configparser.ConfigParser()
|
||||
cfg.read("./config.conf")
|
||||
http_port = cfg.getint("options", "http_port")
|
||||
|
||||
status = self.executer_commande_live(
|
||||
f"./script/process/kill_process_by_port.py {http_port} --kill-tree --nb_parent 2",
|
||||
source_erplibre=False,
|
||||
)
|
||||
|
||||
def download_database_backup_cli(self, show_remote_list=True):
|
||||
database_domain = input("Domain Odoo (ex. https://mondomain.com) : ")
|
||||
if show_remote_list:
|
||||
|
|
@ -1147,12 +1187,38 @@ class TODO:
|
|||
todo_file_browser.exit_program()
|
||||
|
||||
def callback_make_mobile_home(self, dct_config):
|
||||
# Read file
|
||||
default_project_name = "ERPLibre"
|
||||
default_package_name = "ca.erplibre.home"
|
||||
# Read default information
|
||||
if os.path.exists(STRINGS_FILE):
|
||||
tree = ET.parse(STRINGS_FILE)
|
||||
root = tree.getroot()
|
||||
for elem in root.findall("string"):
|
||||
if elem.get("name") == "app_name":
|
||||
default_project_name = elem.text
|
||||
if elem.get("name") == "package_name":
|
||||
default_package_name = elem.text
|
||||
|
||||
default_project_url_name = "https://erplibre.ca"
|
||||
# Read default information
|
||||
dotenv_file = dotenv.find_dotenv(
|
||||
filename=os.path.join(MOBILE_HOME_PATH, "src", ".env.production")
|
||||
)
|
||||
default_project_url_name = dotenv.get_key(
|
||||
dotenv_file, "VITE_WEBSITE_URL"
|
||||
)
|
||||
default_project_note_subject = dotenv.get_key(
|
||||
dotenv_file, "VITE_LABEL_NOTE"
|
||||
)
|
||||
|
||||
default_debug = False
|
||||
project_name = default_project_name
|
||||
project_url_name = default_project_url_name
|
||||
project_principal_subject = default_project_note_subject
|
||||
package_name = default_package_name
|
||||
do_debug = default_debug
|
||||
do_change_picture_menu = False
|
||||
|
||||
do_personalize = input(
|
||||
"Do you want to personalize the mobile application (Y) : "
|
||||
|
|
@ -1160,26 +1226,47 @@ class TODO:
|
|||
if do_personalize.strip().lower() == "y":
|
||||
project_name = (
|
||||
input(
|
||||
f"Your project name, default {default_project_name} : "
|
||||
f'Your project name (Separate by space in title), default "{default_project_name}" : '
|
||||
).strip()
|
||||
or default_project_name
|
||||
)
|
||||
package_name = (
|
||||
input(
|
||||
f'Your package name (separate by . lower case, 3 works like DOMAIN.NAME.OBJECT), default "{default_package_name}" : '
|
||||
).strip()
|
||||
or default_package_name
|
||||
)
|
||||
project_url_name = (
|
||||
input(
|
||||
f"Your project url website, default {default_project_url_name} : "
|
||||
f'Your project url website, default "{default_project_url_name}" : '
|
||||
).strip()
|
||||
or default_project_url_name
|
||||
)
|
||||
project_principal_subject = (
|
||||
input(
|
||||
f'Your project subject, default "{default_project_note_subject}" : '
|
||||
).strip()
|
||||
or default_project_note_subject
|
||||
)
|
||||
do_debug = (
|
||||
input("Do you want debug information (Y) :").strip().lower()
|
||||
input("Compilation with debug information, default No (Y) : ")
|
||||
.strip()
|
||||
.lower()
|
||||
== "y"
|
||||
)
|
||||
do_change_picture_menu = (
|
||||
input(
|
||||
"Want to change picture from menu, you need android-studio (Y) : "
|
||||
)
|
||||
.strip()
|
||||
.lower()
|
||||
== "y"
|
||||
)
|
||||
|
||||
dotenv_file = dotenv.find_dotenv(
|
||||
filename=os.path.join(
|
||||
PATH_MOBILE_PROJECT_HOME, "src", ".env.production"
|
||||
)
|
||||
)
|
||||
# Rename with script bash
|
||||
cmd_client = f'cd {MOBILE_HOME_PATH} && npx cap init "{project_name}" "{package_name}" && ./rename_android.sh "{project_name}" "{package_name}" && npx cap sync android'
|
||||
self.executer_commande_live(cmd_client, source_erplibre=False)
|
||||
|
||||
# dotenv_mobile = dotenv.dotenv_values(dotenv_file)
|
||||
# dotenv_mobile["VITE_TITLE"] = project_name
|
||||
# dotenv_mobile["VITE_WEBSITE_URL"] = project_url_name
|
||||
|
|
@ -1192,12 +1279,36 @@ class TODO:
|
|||
project_url_name,
|
||||
quote_mode="always",
|
||||
)
|
||||
dotenv.set_key(
|
||||
dotenv_file,
|
||||
"VITE_LABEL_NOTE",
|
||||
project_principal_subject,
|
||||
quote_mode="always",
|
||||
)
|
||||
dotenv.set_key(
|
||||
dotenv_file,
|
||||
"VITE_DEBUG_DEV",
|
||||
"true" if do_debug else "false",
|
||||
quote_mode="never",
|
||||
)
|
||||
|
||||
if do_change_picture_menu:
|
||||
status = self.executer_commande_live(
|
||||
f"cd {MOBILE_HOME_PATH} && npx cap open android;bash",
|
||||
source_erplibre=False,
|
||||
new_window=True,
|
||||
)
|
||||
print(
|
||||
"Guide for Android-Studio, wait loading is finish. Right-click to app/New/Image Asset and load your image."
|
||||
)
|
||||
input(
|
||||
"Did you finish to update image with Android-Studio ? Press to continue ..."
|
||||
)
|
||||
cmd_client = "cp ./mobile/erplibre_home_mobile/android/app/src/main/ic_launcher-playstore.png ./mobile/erplibre_home_mobile/src/assets/company_logo.png"
|
||||
self.executer_commande_live(cmd_client, source_erplibre=False)
|
||||
cmd_client = "cp ./mobile/erplibre_home_mobile/android/app/src/main/ic_launcher-playstore.png ./mobile/erplibre_home_mobile/src/assets/imgs/logo.png"
|
||||
self.executer_commande_live(cmd_client, source_erplibre=False)
|
||||
|
||||
status = self.executer_commande_live(
|
||||
"./mobile/compile_and_run.sh", source_erplibre=False
|
||||
)
|
||||
|
|
|
|||
|
|
@ -731,15 +731,24 @@ class TodoUpgrade:
|
|||
if not self.dct_progression.get("state_1_restore_database"):
|
||||
file_name = os.path.basename(self.file_path)
|
||||
image_db_file_path = os.path.join("image_db", file_name)
|
||||
if os.path.exists(image_db_file_path):
|
||||
if not shutil._samefile(self.file_path, image_db_file_path):
|
||||
str_will_copy = (
|
||||
f"🤖 will copy '{self.file_path}' to '{image_db_file_path}'"
|
||||
)
|
||||
if not shutil._samefile(self.file_path, image_db_file_path):
|
||||
do_copy = False
|
||||
if os.path.exists(image_db_file_path):
|
||||
status_overwrite_image_db = input(
|
||||
f"🤖 will copy '{self.file_path}' to '{image_db_file_path}', "
|
||||
f"{str_will_copy}, "
|
||||
f"a file already exist, do you want to continue (y/Y) : "
|
||||
).strip()
|
||||
if status_overwrite_image_db.lower() != "y":
|
||||
return
|
||||
os.remove(image_db_file_path)
|
||||
if status_overwrite_image_db.lower() == "y":
|
||||
do_copy = True
|
||||
os.remove(image_db_file_path)
|
||||
else:
|
||||
print(str_will_copy)
|
||||
do_copy = True
|
||||
|
||||
if do_copy:
|
||||
shutil.copy(self.file_path, image_db_file_path)
|
||||
|
||||
status, cmd_executed = self.todo_upgrade_execute(
|
||||
|
|
|
|||
Loading…
Reference in a new issue