mirror of
https://github.com/inventree/InvenTree.git
synced 2026-05-28 03:49:20 +00:00
feat(backend)!: bump to dj 5.2 lts / py 3.11 (#10730)
* feat(backend): bump to 5.2 lts / python 3.11 This will give us support till 2027-10 (PEP 664) * bump dependencies * fix dflt version * remove 3.9 precaution * changes for 5.2 * changes for py 3.10 * debug command * lower crypto again * another lowering * fix version string * lower minimum version to 3.11 * update refs * fix text * reaking: remove now unsupported OS * disable break * remove temp changes * fix ruff call * fix remaining ruff warnings * remove old arg * lower allauth reqs * replace old method * fix issue with args passing beeing depreceated * add changelog entries * bump dependencies a bit further * fix broken image init for now might need a refactor * fix another test * refactor image name lookup * mroe refactoring * ensure str does not cause an issue * update referenced function * fix cal sig * simplify method and add test * refactor * ignore wrong typings * fix deprecated feature * simplify * ensure image tests do their job * simplify * re-add type check * fix test * fix assertations - wonder how long this was broken * bump to newer versions * bump deps * fix assertation
This commit is contained in:
@@ -14,7 +14,7 @@ pool:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
Python39:
|
Python39:
|
||||||
PYTHON_VERSION: '3.9'
|
PYTHON_VERSION: '3.11'
|
||||||
maxParallel: 3
|
maxParallel: 3
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ on:
|
|||||||
- l10
|
- l10
|
||||||
|
|
||||||
env:
|
env:
|
||||||
python_version: 3.9
|
python_version: 3.11
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ on:
|
|||||||
branches-ignore: ["l10*"]
|
branches-ignore: ["l10*"]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
python_version: 3.9
|
python_version: 3.11
|
||||||
node_version: 20
|
node_version: 20
|
||||||
# The OS version must be set per job
|
# The OS version must be set per job
|
||||||
server_start_sleep: 60
|
server_start_sleep: 60
|
||||||
@@ -334,8 +334,8 @@ jobs:
|
|||||||
continue-on-error: true # continue if a step fails so that coverage gets pushed
|
continue-on-error: true # continue if a step fails so that coverage gets pushed
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python_version: [3.9]
|
python_version: [3.11]
|
||||||
# python_version: [3.9, 3.12] # Disabled due to requirement issues
|
# python_version: [3.11, 3.14] # Disabled due to requirement issues
|
||||||
|
|
||||||
env:
|
env:
|
||||||
INVENTREE_DB_NAME: ./inventree.sqlite
|
INVENTREE_DB_NAME: ./inventree.sqlite
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ on:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
env:
|
env:
|
||||||
python_version: 3.9
|
python_version: 3.11
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stable:
|
stable:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ on:
|
|||||||
- master
|
- master
|
||||||
|
|
||||||
env:
|
env:
|
||||||
python_version: 3.9
|
python_version: 3.11
|
||||||
node_version: 20
|
node_version: 20
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ before:
|
|||||||
- contrib/packager.io/before.sh
|
- contrib/packager.io/before.sh
|
||||||
dependencies:
|
dependencies:
|
||||||
- curl
|
- curl
|
||||||
- "python3.9 | python3.10 | python3.11 | python3.12 | python3.13 | python3.14"
|
- "python3.11 | python3.12 | python3.13 | python3.14"
|
||||||
- "python3.9-venv | python3.10-venv | python3.11-venv | python3.12-venv | python3.13-venv | python3.14-venv"
|
- "python3.11-venv | python3.12-venv | python3.13-venv | python3.14-venv"
|
||||||
- "python3.9-dev | python3.10-dev | python3.11-dev | python3.12-dev | python3.13-dev | python3.14-dev"
|
- "python3.11-dev | python3.12-dev | python3.13-dev | python3.14-dev"
|
||||||
- python3-pip
|
- python3-pip
|
||||||
- python3-cffi
|
- python3-cffi
|
||||||
- python3-brotli
|
- python3-brotli
|
||||||
@@ -35,8 +35,6 @@ dependencies:
|
|||||||
- jq
|
- jq
|
||||||
- "libffi7 | libffi8"
|
- "libffi7 | libffi8"
|
||||||
targets:
|
targets:
|
||||||
ubuntu-20.04: true
|
|
||||||
ubuntu-22.04: true
|
ubuntu-22.04: true
|
||||||
ubuntu-24.04: true
|
ubuntu-24.04: true
|
||||||
debian-11: true
|
|
||||||
debian-12: true
|
debian-12: true
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ repos:
|
|||||||
exclude: mkdocs.yml
|
exclude: mkdocs.yml
|
||||||
- id: mixed-line-ending
|
- id: mixed-line-ending
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.11.13
|
rev: v0.14.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
args: [--preview]
|
args: [--preview]
|
||||||
- id: ruff
|
- id: ruff-check
|
||||||
args: [
|
args: [
|
||||||
--fix,
|
--fix,
|
||||||
# --unsafe-fixes,
|
# --unsafe-fixes,
|
||||||
|
|||||||
Vendored
+1
-1
@@ -44,7 +44,7 @@
|
|||||||
"name": "InvenTree invoke schema",
|
"name": "InvenTree invoke schema",
|
||||||
"type": "debugpy",
|
"type": "debugpy",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "${workspaceFolder}/.venv/lib/python3.9/site-packages/invoke/__main__.py",
|
"program": "${workspaceFolder}/.venv/lib/python3.11/site-packages/invoke/__main__.py",
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
"args": [
|
"args": [
|
||||||
"dev.schema","--ignore-warnings"
|
"dev.schema","--ignore-warnings"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
- Removed python 3.9 / 3.10 support as part of Django 5.2 upgrade in [#10730](https://github.com/inventree/InvenTree/pull/10730)
|
||||||
|
|
||||||
## 1.1.0 - 2025-11-02
|
## 1.1.0 - 2025-11-02
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ gunicorn>=22.0.0
|
|||||||
# LDAP required packages
|
# LDAP required packages
|
||||||
django-auth-ldap # Django integration for ldap auth
|
django-auth-ldap # Django integration for ldap auth
|
||||||
python-ldap # LDAP auth support
|
python-ldap # LDAP auth support
|
||||||
django<5.0 # Force lower to match main project
|
django<6.0 # Force lower to match main project
|
||||||
|
|
||||||
# Upgraded python package installer
|
# Upgraded python package installer
|
||||||
uv
|
uv
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ asgiref==3.10.0 \
|
|||||||
--hash=sha256:aef8a81283a34d0ab31630c9b7dfe70c812c95eba78171367ca8745e88124734 \
|
--hash=sha256:aef8a81283a34d0ab31630c9b7dfe70c812c95eba78171367ca8745e88124734 \
|
||||||
--hash=sha256:d89f2d8cd8b56dada7d52fa7dc8075baa08fb836560710d38c292a7a3f78c04e
|
--hash=sha256:d89f2d8cd8b56dada7d52fa7dc8075baa08fb836560710d38c292a7a3f78c04e
|
||||||
# via django
|
# via django
|
||||||
django==4.2.26 \
|
django==5.2.8 \
|
||||||
--hash=sha256:9398e487bcb55e3f142cb56d19fbd9a83e15bb03a97edc31f408361ee76d9d7a \
|
--hash=sha256:23254866a5bb9a2cfa6004e8b809ec6246eba4b58a7589bc2772f1bcc8456c7f \
|
||||||
--hash=sha256:c96e64fc3c359d051a6306871bd26243db1bd02317472a62ffdbe6c3cae14280
|
--hash=sha256:37e687f7bd73ddf043e2b6b97cfe02fcbb11f2dbb3adccc6a2b18c6daa054d7f
|
||||||
# via
|
# via
|
||||||
# -r contrib/container/requirements.in
|
# -r contrib/container/requirements.in
|
||||||
# django-auth-ldap
|
# django-auth-ldap
|
||||||
@@ -53,77 +53,77 @@ packaging==25.0 \
|
|||||||
# via
|
# via
|
||||||
# gunicorn
|
# gunicorn
|
||||||
# mariadb
|
# mariadb
|
||||||
psycopg[binary, pool]==3.2.11 \
|
psycopg[binary, pool]==3.2.12 \
|
||||||
--hash=sha256:217231b2b6b72fba88281b94241b2f16043ee67f81def47c52a01b72ff0c086a \
|
--hash=sha256:85c08d6f6e2a897b16280e0ff6406bef29b1327c045db06d21f364d7cd5da90b \
|
||||||
--hash=sha256:398bb484ed44361e041c8f804ed7af3d2fcefbffdace1d905b7446c319321706
|
--hash=sha256:8a1611a2d4c16ae37eada46438be9029a35bb959bb50b3d0e1e93c0f3d54c9ee
|
||||||
# via -r contrib/container/requirements.in
|
# via -r contrib/container/requirements.in
|
||||||
psycopg-binary==3.2.11 \
|
psycopg-binary==3.2.12 \
|
||||||
--hash=sha256:00221bfeb9594ca6e01207b032c300fa6f889d918bf0de47f4571c1f9f6e1578 \
|
--hash=sha256:095ccda59042a1239ac2fefe693a336cb5cecf8944a8d9e98b07f07e94e2b78d \
|
||||||
--hash=sha256:110a2036007230416fcc2c17bfe7aaa2c1fa9b6e9d21e2cd551523e3f6489759 \
|
--hash=sha256:0afb71a99871a41dd677d207c6a988d978edde5d6a018bafaed4f9da45357055 \
|
||||||
--hash=sha256:199f88a05dd22133eab2deb30348ef7a70c23d706c8e63fdc904234163c63517 \
|
--hash=sha256:100fdfee763d701f6da694bde711e264aca4c2bc84fb81e1669fb491ce11d219 \
|
||||||
--hash=sha256:1db270e6bdbd183e3908cd9bb832506b99e1f2222a2fc2145f980c3ba1c8c30f \
|
--hash=sha256:13cd057f406d2c8063ae8b489395b089a7f23c39aff223b5ea39f0c4dd640550 \
|
||||||
--hash=sha256:20d41bcd9ac289d44ac1f6151594f7883483b4ad14680a63e04b639dc90c3349 \
|
--hash=sha256:15e226f0d8af85cc8b2435b2e9bc6f0d40febc79eef76cf20fceac4d902a6a7b \
|
||||||
--hash=sha256:23c77dbbffe8ba679213877f7204f4599bd545b65d2d69982fd685a3fea35b11 \
|
--hash=sha256:16db2549a31ccd4887bef05570d95036813ce25fd9810b523ba1c16b0f6cfd90 \
|
||||||
--hash=sha256:260738ae222b41dbefd0d84cb2e150a112f90b41688630f57fdac487ab6d6f38 \
|
--hash=sha256:1c1dbeb8e97d00a33dfa9987776ce3d1c1e4cc251dfbd663b8f9e173f5c89d17 \
|
||||||
--hash=sha256:27eb6367350b75fef882c40cd6f748bfd976db2f8651f7511956f11efc15154f \
|
--hash=sha256:1d7cedecbe0bb60a2e72b1613fba4072a184a6472d6cc9aa99e540217f544e3e \
|
||||||
--hash=sha256:2a438fad4cc081b018431fde0e791b6d50201526edf39522a85164f606c39ddb \
|
--hash=sha256:2598d0e4f2f258da13df0560187b3f1dfc9b8688c46b9d90176360ae5212c3fc \
|
||||||
--hash=sha256:30e2c114d26554ae677088de5d4133cc112344d7a233200fdbf4a2ca5754c7ec \
|
--hash=sha256:26b5927b5880b396231ab6190ee5c8fb47ed3f459b53504ed5419faaf16d3bfb \
|
||||||
--hash=sha256:31f1d5630afa673c37a6327f8e3efa1f17d4e4e42972643b3478b52275233529 \
|
--hash=sha256:294f08b014f08dfd3c9b72408f5e1a0fd187bd86d7a85ead651e32dbd47aa038 \
|
||||||
--hash=sha256:32bd319a68420631a320bb450921c8320641621a92556c97b38b1e116010c344 \
|
--hash=sha256:2aa80ca8d17266507bef853cecefa7d632ffd087883ee7ca92b8a7ea14a1e581 \
|
||||||
--hash=sha256:3bd2c8fb1dec6f93383fbaa561591fa3d676e079f9cb9889af17c3020a19715f \
|
--hash=sha256:2d55009eeddbef54c711093c986daaf361d2c4210aaa1ee905075a3b97a62441 \
|
||||||
--hash=sha256:3f32b09fba85d9e239229bdc5b6254420c02054f6954fe7fbd1ecf1ca93009ed \
|
--hash=sha256:310c95a68a9b948b89d6d187622757d57b6c26cece3c3f7c2cbb645ee36531b2 \
|
||||||
--hash=sha256:478a68d50f34f6203642d245e2046d266c719ab4e593a1bb94c3be5f82e1aee1 \
|
--hash=sha256:32b3e12d9441508f9c4e1424f4478b1a518a90a087cd54be3754e74954934194 \
|
||||||
--hash=sha256:47f6cf8a1d02d25238bdb8741ac641ff0ec22b1c6ff6a2acd057d0da5c712842 \
|
--hash=sha256:356b4266e5cde7b5bbcf232f549dedf7fbed4983daa556042bdec397780e044d \
|
||||||
--hash=sha256:49d76391b225f72dd63fcab87937ccf307ae0f093b5a382eeacf05f19a57c176 \
|
--hash=sha256:385c7b5cfffac115f413b8e32c941c85ea0960e0b94a6ef43bb260f774c54893 \
|
||||||
--hash=sha256:4cae9bdc482e36e825d5102a9f3010e729f33a4ca83fc8a1f439ba16eb61e1f1 \
|
--hash=sha256:3c1e38b1eda54910628f68448598139a9818973755abf77950057372c1fe89a6 \
|
||||||
--hash=sha256:54a30f00a51b9043048b3e7ee806ffd31fc5fbd02a20f0e69d21306ff33dc473 \
|
--hash=sha256:3e9c9e64fb7cda688e9488402611c0be2c81083664117edcc709d15f37faa30f \
|
||||||
--hash=sha256:566d02a0b85b994e40b4f6276b3423c59e8157f10b73bd2e634f8e0a3dfb1890 \
|
--hash=sha256:442f20153415f374ae5753ca618637611a41a3c58c56d16ce55f845d76a3cf7b \
|
||||||
--hash=sha256:5768a9e7d393b2edd3a28de5a6d5850d054a016ed711f7044a9072f19f5e50d5 \
|
--hash=sha256:489b154891f1c995355adeb1077ee3479e9c9bada721b93270c20243bbad6542 \
|
||||||
--hash=sha256:581358e770a4536e546841b78fd0fe318added4a82443bf22d0bbe3109cf9582 \
|
--hash=sha256:48a8e29f3e38fcf8d393b8fe460d83e39c107ad7e5e61cd3858a7569e0554a39 \
|
||||||
--hash=sha256:58997db1aa48a1119e26c1c2f893d1c92339bd3be5d1f25334f22eaeaeeca90e \
|
--hash=sha256:49582c3b6d578bdaab2932b59f70b1bd93351ed4d594b2c97cea1611633c9de1 \
|
||||||
--hash=sha256:58d8f9f80ae79ba7f2a0509424939236220d7d66a4f8256ae999b882cc58065b \
|
--hash=sha256:58ed30d33c25d7dc8d2f06285e88493147c2a660cc94713e4b563a99efb80a1f \
|
||||||
--hash=sha256:592fb928efe0674a7400af914bcf931eb5267d36237925947aaecf63bd9a91aa \
|
--hash=sha256:5b6e505618cb376a7a7d6af86833a8f289833fe4cc97541d7100745081dc31bd \
|
||||||
--hash=sha256:5bc571786a256a2fa2d8f13b5ecf714020b753bc76c2fa6d308e46751946dc31 \
|
--hash=sha256:66a031f22e4418016990446d3e38143826f03ad811b9f78f58e2afbc1d343f7a \
|
||||||
--hash=sha256:5f6f948ff1cd252003ff534d7b50a2b25453b4212b283a7514ff8751bdb68c37 \
|
--hash=sha256:6a898717ab560db393355c6ecf39b8c534f252afc3131480db1251e061090d3a \
|
||||||
--hash=sha256:5fb27dd9c52ae13cb4de90244207155b694f76a75a816115ead2d573f40e1e36 \
|
--hash=sha256:7130effd0517881f3a852eff98729d51034128f0737f64f0d1c7ea8343d77bd7 \
|
||||||
--hash=sha256:6688807ed07436c18e9946d01372bc80b9d20b7732cde27de9313e0860910c84 \
|
--hash=sha256:72fd979e410ba7805462817ef8ed6f37dd75f9f4ae109bdb8503e013ccecb80b \
|
||||||
--hash=sha256:6b9632c42f76d5349e7dd50025cff02688eb760b258e891ad2c6428e7e4917d5 \
|
--hash=sha256:77690f0bf08356ca00fc357f50a5980c7a25f076c2c1f37d9d775a278234fefd \
|
||||||
--hash=sha256:720e19ff2d1c425b6be18bd20ba35010c7927e492bcfecbae1085a89caa7db7c \
|
--hash=sha256:79de3cc5adbf51677009a8fda35ac9e9e3686d5595ab4b0c43ec7099ece6aeb5 \
|
||||||
--hash=sha256:749d23fbfd642a7abfef5fc0f6ca185fa82a2c0f895e6eab42c3f2a5d88f6011 \
|
--hash=sha256:7b9a99ded7d19b24d3b6fa632b58e52bbdecde7e1f866c3b23d0c27b092af4e3 \
|
||||||
--hash=sha256:7608c9fa58b85426093ab8777080e8f134d89915c05c51fa270e7aee317f2b38 \
|
--hash=sha256:802bd01fb18a0acb0dea491f69a9a2da6034f33329a62876ab5b558a1fb66b45 \
|
||||||
--hash=sha256:766089fdaa8af1b5f7e2ec9fd7ad190c865e226b4fb0e7b1bd8dbcd62b5b923e \
|
--hash=sha256:8335d989a4e94df2ccd8a1acbba9d03c4157ea8d73b65b79d447c6dc10b001d8 \
|
||||||
--hash=sha256:7744b4ed1f3b76fe37de7e9ef98014482fe74b6d3dfe1026cc4cfb4b4404e74f \
|
--hash=sha256:89b3c5201ca616d69ca0c3c0003ca18f7170a679c445c7e386ebfb4f29aa738e \
|
||||||
--hash=sha256:7b3c5474dbad63bcccb8d14d4d4c7c19f1dc6f8e8c1914cbc771d261cf8eddca \
|
--hash=sha256:8ffe75fe6be902dadd439adf4228c98138a992088e073ede6dd34e7235f4e03e \
|
||||||
--hash=sha256:81e57d1f00af9b7414c8d00ac77892b3786ddd69a23c27dee47cae8fd3543b07 \
|
--hash=sha256:909de94de7dd4d6086098a5755562207114c9638ec42c52d84c8a440c45fe084 \
|
||||||
--hash=sha256:82fe30afbdd66fbdad583b02baad5c15930a3dc8a3756d2ae15fc874e9be8ec8 \
|
--hash=sha256:940ac69ef6e89c17b3d30f3297a2ad03efdd06a4b1857f81bc533a9108a90eb9 \
|
||||||
--hash=sha256:8792e502a16a0b28d9fd23571fe492271a00c4b2b55f6c0b8377e47032758cd3 \
|
--hash=sha256:95f2806097a49bfd57e0c6a178f77b99487c53c157d9d507aee9c40dd58efdb4 \
|
||||||
--hash=sha256:91268f04380964a5e767f8102d05f1e23312ddbe848de1a9514b08b3fc57d354 \
|
--hash=sha256:9c674887d1e0d4384c06c822bc7fcfede4952742e232ec1e76b5a6ae39a3ddd4 \
|
||||||
--hash=sha256:9b4b0fc4e774063ae64c92cc57e2b10160150de68c96d71743218159d953869d \
|
--hash=sha256:9fdf3a0c24822401c60c93640da69b3dfd4d9f29c3a8d797244fe22bfe592823 \
|
||||||
--hash=sha256:9bdc762600fcc8e4ad3224734a4e70cc226207fd8f2de47c36b115efeed01782 \
|
--hash=sha256:ab02b7d138768fd6ac4230e45b073f7b9fd688d88c04f24c34df4a250a94d066 \
|
||||||
--hash=sha256:9ea3ebe1706fd78d6ac0dd1cf692a77cfacd5ba967c82128f9863a5e48f63c47 \
|
--hash=sha256:acb1811219a4144539f0baee224a11a2aa323a739c349799cf52f191eb87bc52 \
|
||||||
--hash=sha256:9f12a34bddaeffa7840a61163595ec0d70a9db855896865dcfbb731510014484 \
|
--hash=sha256:bfd632f7038c76b0921f6d5621f5ba9ecabfad3042fa40e5875db11771d2a5de \
|
||||||
--hash=sha256:a3a59d404e1fb8ec47116f66f5adf48a8993a8aac0dad0395a923155fd55ee38 \
|
--hash=sha256:ce68839da386f137bc8d814fdbeede8f89916b8605e3593a85b504a859243af9 \
|
||||||
--hash=sha256:b051aa1e67f0d03ccdb4503d716f22da56229896526f0aa721e5a199baa9e5d4 \
|
--hash=sha256:d369e79ad9647fc8217cbb51bbbf11f9a1ffca450be31d005340157ffe8e91b3 \
|
||||||
--hash=sha256:b2fa94ce40bc4b408149d83a6204fc5e53c3e9d3cd5b749de2e7e9671a049cf7 \
|
--hash=sha256:dc68094e00a5a7e8c20de1d3a0d5e404a27f522e18f8eb62bbbc9f865c3c81ef \
|
||||||
--hash=sha256:c45f61202e5691090a697e599997eaffa3ec298209743caa4fd346145acabafe \
|
--hash=sha256:deeb06b7141f3a577c3aa8562307e2747580ae43d705a0482603a2c1f110d046 \
|
||||||
--hash=sha256:c594c199869099c59c85b9f4423370b6212491fb929e7fcda0da1768761a2c2c \
|
--hash=sha256:e0b5ccd03ca4749b8f66f38608ccbcb415cbd130d02de5eda80d042b83bee90e \
|
||||||
--hash=sha256:d27f51b8ce291da4af749ef850adb4520bfe52c2ff4677402c719ff35af03f00 \
|
--hash=sha256:ea049c8d33c4f4e6b030d5a68123c0ccd2ffb77d4035f073db97187b49b6422f \
|
||||||
--hash=sha256:d59db908d9baaa057a43dd5aa8352f3e3de4b8c57f295172d5fe521e97d6c39d \
|
--hash=sha256:ea9751310b840186379c949ede5a5129b31439acdb929f3003a8685372117ed8 \
|
||||||
--hash=sha256:d7e490848d7bedf6c1d2180233a33d31d554a1b0823f80a0236ebb7d3b6caf12 \
|
--hash=sha256:ec82fa5134517af44e28a30c38f34384773a0422ffd545fd298433ea9f2cc5a9 \
|
||||||
--hash=sha256:e3b6328bc2f3ca233f9a5f08d266089b96a534eca9ee4e45cb92d0a8d4629d9c \
|
--hash=sha256:eedc410f82007038030650aa58f620f9fe0009b9d6b04c3dc71cbd3bae5b2675 \
|
||||||
--hash=sha256:e3f5887019dfb094c60e7026968ca3a964ca16305807ba5e43f9a78483767d5f \
|
--hash=sha256:ef40601b959cc1440deaf4d53472ab54fa51036c37189cf3fe5500559ac25347 \
|
||||||
--hash=sha256:e7575ca710277cc3e9257ff803a3e0e3cb7cc1b7851639cb783a7cd55ebfc815 \
|
--hash=sha256:ef92d5ba6213de060d1390b1f71f5c3b2fbb00b4d55edee39f3b07234538b64a \
|
||||||
--hash=sha256:e7f4dff472a529c9027f294c8842ab535bbed7e2928fe1f4fc28b27f2463d6d5 \
|
--hash=sha256:efab679a2c7d1bf7d0ec0e1ecb47fe764945eff75bb4321f2e699b30a12db9b3 \
|
||||||
--hash=sha256:eab6959fade522e586b8ec37d3fe337ce10861965edef3292f52e66e36dc375d \
|
--hash=sha256:f33c9e12ed05e579b7fb3c8fdb10a165f41459394b8eb113e7c377b2bd027f61 \
|
||||||
--hash=sha256:f5e7415b5d0f58edf2708842c66605092df67f3821161d861b09695fc326c4de \
|
--hash=sha256:f3bae4be7f6781bf6c9576eedcd5e1bb74468126fa6de991e47cdb1a8ea3a42a \
|
||||||
--hash=sha256:f72146ad5b69ea177c2707578e5a4a9422b79e50d5a80992dabc5619b0929771 \
|
--hash=sha256:f6ba1fe35fd215813dac4544a5ffc90f13713b29dd26e9e5be97ba53482bf6d6 \
|
||||||
--hash=sha256:fa2aa5094dc962967ca0978c035b3ef90329b802501ef12a088d3bac6a55598e \
|
--hash=sha256:f7c81bc60560be9eb3c23601237765069ebfa9881097ce19ca6b5ea17c5faa8f \
|
||||||
--hash=sha256:fe5e3648e855df4fba1d70c18aef18c9880ea8d123fdfae754c18787c8cb37b3 \
|
--hash=sha256:f8107968a9eadb451cfa6cf86036006fdde32a83cd39c26c9ca46765e653b547 \
|
||||||
--hash=sha256:ff64883cff66fe797cd958c0ff7f53fc36a28239b9e0dc80189ce1c03ce47153
|
--hash=sha256:f821e0c8a8fdfddfa71acb4f462d7a4c5aae1655f3f5e078970dbe9f19027386
|
||||||
# via psycopg
|
# via psycopg
|
||||||
psycopg-pool==3.2.6 \
|
psycopg-pool==3.2.7 \
|
||||||
--hash=sha256:0f92a7817719517212fbfe2fd58b8c35c1850cdd2a80d36b581ba2085d9148e5 \
|
--hash=sha256:4b47bb59d887ef5da522eb63746b9f70e2faf967d34aac4f56ffc65e9606728f \
|
||||||
--hash=sha256:5887318a9f6af906d041a0b1dc1c60f8f0dda8340c2572b74e10907b51ed5da7
|
--hash=sha256:a77d531bfca238e49e5fb5832d65b98e69f2c62bfda3d2d4d833696bdc9ca54b
|
||||||
# via psycopg
|
# via psycopg
|
||||||
pyasn1==0.6.1 \
|
pyasn1==0.6.1 \
|
||||||
--hash=sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 \
|
--hash=sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 \
|
||||||
@@ -229,26 +229,26 @@ typing-extensions==4.15.0 \
|
|||||||
# via
|
# via
|
||||||
# psycopg
|
# psycopg
|
||||||
# psycopg-pool
|
# psycopg-pool
|
||||||
uv==0.9.7 \
|
uv==0.9.8 \
|
||||||
--hash=sha256:0fdbfad5b367e7a3968264af6da5bbfffd4944a90319042f166e8df1a2d9de09 \
|
--hash=sha256:0f03bc413c933dbf850ad0dc2dba3df6b80c860a5c65cd767add49da19dadef0 \
|
||||||
--hash=sha256:134e0daac56f9e399ccdfc9e4635bc0a13c234cad9224994c67bae462e07399a \
|
--hash=sha256:14670bf55ecb5cfd0f3654fbf51c58a21dec3ad8ab531079b3ed8599271dc77b \
|
||||||
--hash=sha256:1aaf79b4234400e9e2fbf5b50b091726ccbb0b6d4d032edd3dfd4c9673d89dca \
|
--hash=sha256:1b8b5bdcda3e10ea70b618d0609acddc5c725cb58d4caf933030ddedd7c2e98f \
|
||||||
--hash=sha256:34fe0af83fcafb9e2b786f4bd633a06c878d548a7c479594ffb5607db8778471 \
|
--hash=sha256:40253d00c1e900a0a61b132b1e0dd4aa83575cfd5302d3671899b6de29b1ef67 \
|
||||||
--hash=sha256:555ee72146b8782c73d755e4a21c9885c6bfc81db0ffca2220d52dddae007eb7 \
|
--hash=sha256:50d130c46d97d7f10675ebea8608b7b4722c84b5745cd1bb0c8ae6d7984c05d5 \
|
||||||
--hash=sha256:56a440ccde7624a7bc070e1c2492b358c67aea9b8f17bc243ea27c5871c8d02c \
|
--hash=sha256:543693def38fa41b9706aba391111fe8d9dd6be86899d76f9581faf045ac1cb6 \
|
||||||
--hash=sha256:62b315f62669899076a1953fba6baf50bd2b57f66f656280491331dcedd7e6c6 \
|
--hash=sha256:5af28f1645eb3c50fd34a78508792db2d0799816f4eb5f55e1e6e2c724dfb125 \
|
||||||
--hash=sha256:635e82c2d0d8b001618af82e4f2724350f15814f6462a71b3ebd44adec21f03c \
|
--hash=sha256:6a01d7cd41953ffac583139b10ad1df004a67c0246a6b694eb5bcdbc8c99deaf \
|
||||||
--hash=sha256:7019f4416925f4091b9d28c1cf3e8444cf910c4ede76bdf1f6b9a56ca5f97985 \
|
--hash=sha256:6df2e16f6df32018047c60bab2c0284868ad5c309addba9183ea2eeb71746bf0 \
|
||||||
--hash=sha256:777bb1de174319245a35e4f805d3b4484d006ebedae71d3546f95e7c28a5f436 \
|
--hash=sha256:7038a552159f2291dd0d1f4f66a36261b5f3ed5fcd92e2869186f8e910b2c935 \
|
||||||
--hash=sha256:89697fa0d7384ba047daf75df844ee7800235105e41d08e0c876861a2b4aa90e \
|
--hash=sha256:75671150d6eb9d5ee829e1fdb8cf86b8e44a66d27cbb996fe807e986c4107b5d \
|
||||||
--hash=sha256:8cf6bc2482d1293cc630f66b862b494c09acda9b7faff7307ef52667a2b3ad49 \
|
--hash=sha256:87c3b65b6d5fcbdeab199d54c74fbf75de19cb534a690c936c5616478a038576 \
|
||||||
--hash=sha256:b5f1fb8203a77853db176000e8f30d5815ab175dc46199db059f97a72fc51110 \
|
--hash=sha256:99b18bfe92c33c3862b65d74677697e799763e669e0064685f405e7e27517f25 \
|
||||||
--hash=sha256:bb8bfcc2897f7653522abc2cae80233af756ad857bfbbbbe176f79460cbba417 \
|
--hash=sha256:9f2f3576c4518ff4f15e48dbca70585a513523c4738bc8cc2e48b20fd1190ce3 \
|
||||||
--hash=sha256:bcf878528bd079fe8ae15928b5dfa232fac8b0e1854a2102da6ae1a833c31276 \
|
--hash=sha256:a4010b3fdabbb3c4f2cf2f7aa3bf6002d00049dcbc54ce0ee5ada32a933b2290 \
|
||||||
--hash=sha256:c9810ee8173dce129c49b338d5e97f3d7c7e9435f73e0b9b26c2f37743d3bb9e \
|
--hash=sha256:bb0f8e83c2a2fc5a802e930cc8a7b71ab068180300a3f27ba38037f9fcb3d430 \
|
||||||
--hash=sha256:d13da6521d4e841b1e0a9fda82e793dcf8458a323a9e8955f50903479d0bfa97 \
|
--hash=sha256:cdbfadca9522422ab9820f5ada071c9c5c869bcd6fee719d20d91d5ec85b2a7d \
|
||||||
--hash=sha256:d6e5fe28ca05a4b576c0e8da5f69251dc187a67054829cfc4afb2bfa1767114b \
|
--hash=sha256:d93a2227d23e81ab3a16c30363559afc483e8aca40ea9343b3f326a9a41718c9 \
|
||||||
--hash=sha256:edd768f6730bba06aa10fdbd80ee064569f7236806f636bf65b68136a430aad0
|
--hash=sha256:f52c6a99197028a314d4c1825f7ccb696eb9a88b822d2e2f17046266c75e543e
|
||||||
# via -r contrib/container/requirements.in
|
# via -r contrib/container/requirements.in
|
||||||
wheel==0.45.1 \
|
wheel==0.45.1 \
|
||||||
--hash=sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729 \
|
--hash=sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729 \
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
#
|
#
|
||||||
Color_Off='\033[0m'
|
Color_Off='\033[0m'
|
||||||
On_Red='\033[41m'
|
On_Red='\033[41m'
|
||||||
PYTHON_FROM=9
|
PYTHON_FROM=11
|
||||||
PYTHON_TO=14
|
PYTHON_TO=15
|
||||||
|
|
||||||
function detect_docker() {
|
function detect_docker() {
|
||||||
if [ -n "$(grep docker </proc/1/cgroup)" ]; then
|
if [ -n "$(grep docker </proc/1/cgroup)" ]; then
|
||||||
@@ -61,7 +61,7 @@ function detect_python() {
|
|||||||
echo "# POI07| No python environment found - using environment variable: ${SETUP_PYTHON}"
|
echo "# POI07| No python environment found - using environment variable: ${SETUP_PYTHON}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Try to detect a python between 3.9 and 3.12 in reverse order
|
# Try to detect a python between lowest and highest supported in reverse order
|
||||||
if [ -z "$(which ${SETUP_PYTHON})" ]; then
|
if [ -z "$(which ${SETUP_PYTHON})" ]; then
|
||||||
echo "# POI07| Trying to detecting python3.${PYTHON_FROM} to python3.${PYTHON_TO} - using newest version"
|
echo "# POI07| Trying to detecting python3.${PYTHON_FROM} to python3.${PYTHON_TO} - using newest version"
|
||||||
for i in $(seq $PYTHON_TO -1 $PYTHON_FROM); do
|
for i in $(seq $PYTHON_TO -1 $PYTHON_FROM); do
|
||||||
@@ -79,7 +79,7 @@ function detect_python() {
|
|||||||
echo "${On_Red}"
|
echo "${On_Red}"
|
||||||
echo "# POI07| Python ${SETUP_PYTHON} not found - aborting!"
|
echo "# POI07| Python ${SETUP_PYTHON} not found - aborting!"
|
||||||
echo "# POI07| Please ensure python can be executed with the command '$SETUP_PYTHON' by the current user '$USER'."
|
echo "# POI07| Please ensure python can be executed with the command '$SETUP_PYTHON' by the current user '$USER'."
|
||||||
echo "# POI07| If you are using a different python version, please set the environment variable SETUP_PYTHON to the correct command - eg. 'python3.10'."
|
echo "# POI07| If you are using a different python version, please set the environment variable SETUP_PYTHON to the correct command - eg. 'python3.11'."
|
||||||
echo "${Color_Off}"
|
echo "${Color_Off}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export DATA_DIR=${APP_HOME}/data
|
|||||||
export SETUP_NGINX_FILE=${SETUP_NGINX_FILE:-/etc/nginx/sites-enabled/inventree.conf}
|
export SETUP_NGINX_FILE=${SETUP_NGINX_FILE:-/etc/nginx/sites-enabled/inventree.conf}
|
||||||
export SETUP_ADMIN_PASSWORD_FILE=${CONF_DIR}/admin_password.txt
|
export SETUP_ADMIN_PASSWORD_FILE=${CONF_DIR}/admin_password.txt
|
||||||
export SETUP_NO_CALLS=${SETUP_NO_CALLS:-false}
|
export SETUP_NO_CALLS=${SETUP_NO_CALLS:-false}
|
||||||
export SETUP_PYTHON=${SETUP_PYTHON:-python3.9}
|
export SETUP_PYTHON=${SETUP_PYTHON:-python3.11}
|
||||||
export SETUP_ADMIN_NOCREATION=${SETUP_ADMIN_NOCREATION:-false}
|
export SETUP_ADMIN_NOCREATION=${SETUP_ADMIN_NOCREATION:-false}
|
||||||
# SETUP_DEBUG can be set to get debug info
|
# SETUP_DEBUG can be set to get debug info
|
||||||
# SETUP_EXTRA_PIP can be set to install extra pip packages
|
# SETUP_EXTRA_PIP can be set to install extra pip packages
|
||||||
|
|||||||
+2
-2
@@ -357,9 +357,9 @@ extra:
|
|||||||
# provider: google
|
# provider: google
|
||||||
# property: UA-143467500-1
|
# property: UA-143467500-1
|
||||||
|
|
||||||
min_python_version: 3.9
|
min_python_version: 3.10
|
||||||
min_invoke_version: 2.0.0
|
min_invoke_version: 2.0.0
|
||||||
django_version: 4.2
|
django_version: 5.2
|
||||||
docker_postgres_version: 16
|
docker_postgres_version: 16
|
||||||
|
|
||||||
version:
|
version:
|
||||||
|
|||||||
@@ -152,10 +152,6 @@ essentials-openapi==1.2.1 \
|
|||||||
--hash=sha256:70b06d80a8d9cb7634b702903cfe8fcfc48e6fc054e744eab514bb7bc37f00c9 \
|
--hash=sha256:70b06d80a8d9cb7634b702903cfe8fcfc48e6fc054e744eab514bb7bc37f00c9 \
|
||||||
--hash=sha256:d0085cde3ceaa25e6fc4983d8372ac0185909e3fa2791f498280379bb3c86c62
|
--hash=sha256:d0085cde3ceaa25e6fc4983d8372ac0185909e3fa2791f498280379bb3c86c62
|
||||||
# via neoteroi-mkdocs
|
# via neoteroi-mkdocs
|
||||||
exceptiongroup==1.3.0 \
|
|
||||||
--hash=sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10 \
|
|
||||||
--hash=sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88
|
|
||||||
# via anyio
|
|
||||||
ghp-import==2.1.0 \
|
ghp-import==2.1.0 \
|
||||||
--hash=sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619 \
|
--hash=sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619 \
|
||||||
--hash=sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343
|
--hash=sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343
|
||||||
@@ -197,14 +193,6 @@ idna==3.10 \
|
|||||||
# anyio
|
# anyio
|
||||||
# httpx
|
# httpx
|
||||||
# requests
|
# requests
|
||||||
importlib-metadata==8.7.0 \
|
|
||||||
--hash=sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000 \
|
|
||||||
--hash=sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd
|
|
||||||
# via
|
|
||||||
# markdown
|
|
||||||
# mkdocs
|
|
||||||
# mkdocs-get-deps
|
|
||||||
# mkdocstrings
|
|
||||||
jinja2==3.1.6 \
|
jinja2==3.1.6 \
|
||||||
--hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
|
--hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
|
||||||
--hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
|
--hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
|
||||||
@@ -551,9 +539,6 @@ typing-extensions==4.14.1 \
|
|||||||
# via
|
# via
|
||||||
# anyio
|
# anyio
|
||||||
# beautifulsoup4
|
# beautifulsoup4
|
||||||
# exceptiongroup
|
|
||||||
# gitpython
|
|
||||||
# mkdocstrings-python
|
|
||||||
urllib3==2.5.0 \
|
urllib3==2.5.0 \
|
||||||
--hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \
|
--hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \
|
||||||
--hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc
|
--hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc
|
||||||
@@ -594,7 +579,3 @@ wcmatch==10.1 \
|
|||||||
--hash=sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a \
|
--hash=sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a \
|
||||||
--hash=sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af
|
--hash=sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af
|
||||||
# via mkdocs-include-markdown-plugin
|
# via mkdocs-include-markdown-plugin
|
||||||
zipp==3.23.0 \
|
|
||||||
--hash=sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e \
|
|
||||||
--hash=sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166
|
|
||||||
# via importlib-metadata
|
|
||||||
|
|||||||
+1
-1
@@ -97,7 +97,7 @@ skip-magic-trailing-comma = true
|
|||||||
line-ending = "auto"
|
line-ending = "auto"
|
||||||
|
|
||||||
[tool.uv.pip]
|
[tool.uv.pip]
|
||||||
python-version = "3.9.2"
|
python-version = "3.11.0"
|
||||||
no-strip-extras=true
|
no-strip-extras=true
|
||||||
generate-hashes=true
|
generate-hashes=true
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ python:
|
|||||||
build:
|
build:
|
||||||
os: "ubuntu-22.04"
|
os: "ubuntu-22.04"
|
||||||
tools:
|
tools:
|
||||||
python: "3.9"
|
python: "3.11"
|
||||||
jobs:
|
jobs:
|
||||||
post_install:
|
post_install:
|
||||||
- pip install -U invoke
|
- pip install -U invoke
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ def read_license_file(path: Path) -> list:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = json.loads(path.read_text())
|
data = json.loads(path.read_text(encoding='utf-8'))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception("Failed to parse license file '%s': %s", path, e)
|
logger.exception("Failed to parse license file '%s': %s", path, e)
|
||||||
return []
|
return []
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
"""InvenTree API version information."""
|
"""InvenTree API version information."""
|
||||||
|
|
||||||
# InvenTree API version
|
# InvenTree API version
|
||||||
INVENTREE_API_VERSION = 423
|
INVENTREE_API_VERSION = 424
|
||||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||||
|
|
||||||
INVENTREE_API_TEXT = """
|
INVENTREE_API_TEXT = """
|
||||||
|
|
||||||
|
v424 -> 2025-11-05 : https://github.com/inventree/InvenTree/pull/10730
|
||||||
|
- Adds more lower / upper bounds to integer fields in the API schema due to bump to Django 5.2- no functional changes
|
||||||
|
|
||||||
v423 -> 2025-11-05 : https://github.com/inventree/InvenTree/pull/10772
|
v423 -> 2025-11-05 : https://github.com/inventree/InvenTree/pull/10772
|
||||||
- Adds "category_detail" field to BomItem API endpoints
|
- Adds "category_detail" field to BomItem API endpoints
|
||||||
- Adds "category_detail" field to BuildLine API endpoints
|
- Adds "category_detail" field to BuildLine API endpoints
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ class InvenTreeConfig(AppConfig):
|
|||||||
new_user = user.objects.create_superuser(
|
new_user = user.objects.create_superuser(
|
||||||
add_user, add_email, add_password
|
add_user, add_email, add_password
|
||||||
)
|
)
|
||||||
logger.info('User %s was created!', str(new_user))
|
logger.info('User %s was created!', new_user)
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
logger.warning('The user "%s" could not be created', add_user)
|
logger.warning('The user "%s" could not be created', add_user)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
@@ -109,7 +108,7 @@ class InvenTreeMailLoggingBackend(BaseEmailBackend):
|
|||||||
return self.backend.open()
|
return self.backend.open()
|
||||||
|
|
||||||
def send_messages(
|
def send_messages(
|
||||||
self, email_messages: list[Union[EmailMessage, EmailMultiAlternatives]]
|
self, email_messages: list[EmailMessage | EmailMultiAlternatives]
|
||||||
) -> int:
|
) -> int:
|
||||||
"""Send email messages and log them to the database.
|
"""Send email messages and log them to the database.
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import random
|
|||||||
import shutil
|
import shutil
|
||||||
import string
|
import string
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Union
|
from typing import Optional
|
||||||
|
|
||||||
logger = logging.getLogger('inventree')
|
logger = logging.getLogger('inventree')
|
||||||
CONFIG_DATA = None
|
CONFIG_DATA = None
|
||||||
@@ -177,7 +177,7 @@ def get_config_file(create=True) -> Path:
|
|||||||
return cfg_filename
|
return cfg_filename
|
||||||
|
|
||||||
|
|
||||||
def load_config_data(set_cache: bool = False) -> Union[map, None]:
|
def load_config_data(set_cache: bool = False) -> map | None:
|
||||||
"""Load configuration data from the config file.
|
"""Load configuration data from the config file.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -392,7 +392,7 @@ def get_plugin_dir():
|
|||||||
return get_setting('INVENTREE_PLUGIN_DIR', 'plugin_dir')
|
return get_setting('INVENTREE_PLUGIN_DIR', 'plugin_dir')
|
||||||
|
|
||||||
|
|
||||||
def get_secret_key(return_path: bool = False) -> Union[str, Path]:
|
def get_secret_key(return_path: bool = False) -> str | Path:
|
||||||
"""Return the secret key value which will be used by django.
|
"""Return the secret key value which will be used by django.
|
||||||
|
|
||||||
Following options are tested, in descending order of preference:
|
Following options are tested, in descending order of preference:
|
||||||
@@ -436,7 +436,7 @@ def get_secret_key(return_path: bool = False) -> Union[str, Path]:
|
|||||||
return secret_key_file.read_text().strip()
|
return secret_key_file.read_text().strip()
|
||||||
|
|
||||||
|
|
||||||
def get_oidc_private_key(return_path: bool = False) -> Union[str, Path]:
|
def get_oidc_private_key(return_path: bool = False) -> str | Path:
|
||||||
"""Return the private key for OIDC authentication.
|
"""Return the private key for OIDC authentication.
|
||||||
|
|
||||||
Following options are tested, in descending order of preference:
|
Following options are tested, in descending order of preference:
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class InvenTreeDateFilter(rest_filters.DateFilter):
|
|||||||
if settings.USE_TZ and value is not None:
|
if settings.USE_TZ and value is not None:
|
||||||
tz = timezone.get_current_timezone()
|
tz = timezone.get_current_timezone()
|
||||||
value = datetime(value.year, value.month, value.day)
|
value = datetime(value.year, value.month, value.day)
|
||||||
value = make_aware(value, timezone=tz, is_dst=True)
|
value = make_aware(value, timezone=tz)
|
||||||
|
|
||||||
return super().filter(qs, value)
|
return super().filter(qs, value)
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,10 @@ import hashlib
|
|||||||
import inspect
|
import inspect
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
import os
|
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
from decimal import Decimal, InvalidOperation
|
from decimal import Decimal, InvalidOperation
|
||||||
from typing import Optional, TypeVar, Union
|
from typing import Optional, TypeVar
|
||||||
from wsgiref.util import FileWrapper
|
from wsgiref.util import FileWrapper
|
||||||
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||||
|
|
||||||
@@ -17,6 +16,7 @@ from django.conf import settings
|
|||||||
from django.contrib.staticfiles.storage import StaticFilesStorage
|
from django.contrib.staticfiles.storage import StaticFilesStorage
|
||||||
from django.core.exceptions import FieldError, ValidationError
|
from django.core.exceptions import FieldError, ValidationError
|
||||||
from django.core.files.storage import default_storage
|
from django.core.files.storage import default_storage
|
||||||
|
from django.db.models.fields.files import ImageFieldFile
|
||||||
from django.http import StreamingHttpResponse
|
from django.http import StreamingHttpResponse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@@ -28,6 +28,7 @@ import structlog
|
|||||||
from bleach import clean
|
from bleach import clean
|
||||||
from djmoney.money import Money
|
from djmoney.money import Money
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
from stdimage.models import StdImageField, StdImageFieldFile
|
||||||
|
|
||||||
from common.currency import currency_code_default
|
from common.currency import currency_code_default
|
||||||
|
|
||||||
@@ -127,7 +128,7 @@ def extract_int(
|
|||||||
return ref_int
|
return ref_int
|
||||||
|
|
||||||
|
|
||||||
def generateTestKey(test_name: Union[str, None]) -> str:
|
def generateTestKey(test_name: str | None) -> str:
|
||||||
"""Generate a test 'key' for a given test name. This must not have illegal chars as it will be used for dict lookup in a template.
|
"""Generate a test 'key' for a given test name. This must not have illegal chars as it will be used for dict lookup in a template.
|
||||||
|
|
||||||
Tests must be named such that they will have unique keys.
|
Tests must be named such that they will have unique keys.
|
||||||
@@ -175,11 +176,48 @@ def constructPathString(path: list[str], max_chars: int = 250) -> str:
|
|||||||
return pathstring
|
return pathstring
|
||||||
|
|
||||||
|
|
||||||
def getMediaUrl(filename):
|
def getMediaUrl(file: StdImageFieldFile | ImageFieldFile, name: str | None = None):
|
||||||
"""Return the qualified access path for the given file, under the media directory."""
|
"""Return the qualified access path for the given file, under the media directory."""
|
||||||
|
if not isinstance(file, StdImageFieldFile):
|
||||||
|
raise ValueError('file_obj must be an instance of StdImageFieldFile')
|
||||||
|
if name is not None:
|
||||||
|
file = regenerate_imagefile(file, name)
|
||||||
if settings.STORAGE_TARGET == StorageBackends.S3:
|
if settings.STORAGE_TARGET == StorageBackends.S3:
|
||||||
return str(filename)
|
return str(file.url)
|
||||||
return os.path.join(MEDIA_URL, str(filename))
|
return os.path.join(MEDIA_URL, str(file.url))
|
||||||
|
|
||||||
|
|
||||||
|
def regenerate_imagefile(_file, _name: str):
|
||||||
|
"""Regenerate a file object for a given variation name.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
_file: Original file object
|
||||||
|
_name: Name of the variation (e.g. 'thumbnail', 'preview')
|
||||||
|
"""
|
||||||
|
name = _file.field.attr_class.get_variation_name(_file.name, _name)
|
||||||
|
return ImageFieldFile(_file.instance, _file, name) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
def image2name(img_obj: StdImageField, do_preview: bool, do_thumbnail: bool):
|
||||||
|
"""Convert an image object to a filename string.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
img_obj: Image object
|
||||||
|
do_preview: Return preview image name
|
||||||
|
do_thumbnail: Return thumbnail image name
|
||||||
|
"""
|
||||||
|
|
||||||
|
def extract(ref: str):
|
||||||
|
return None if not hasattr(img_obj, ref) else getattr(img_obj, ref).name
|
||||||
|
|
||||||
|
if not img_obj:
|
||||||
|
return None
|
||||||
|
elif do_preview:
|
||||||
|
return extract('preview')
|
||||||
|
elif do_thumbnail:
|
||||||
|
return extract('thumbnail')
|
||||||
|
else:
|
||||||
|
return img_obj.name
|
||||||
|
|
||||||
|
|
||||||
def getStaticUrl(filename):
|
def getStaticUrl(filename):
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Code for managing email functionality in InvenTree."""
|
"""Code for managing email functionality in InvenTree."""
|
||||||
|
|
||||||
from typing import Optional, Union
|
from typing import Optional
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
@@ -8,7 +8,6 @@ import structlog
|
|||||||
from allauth.account.models import EmailAddress
|
from allauth.account.models import EmailAddress
|
||||||
|
|
||||||
import InvenTree.ready
|
import InvenTree.ready
|
||||||
import InvenTree.tasks
|
|
||||||
from common.models import Priority, issue_mail
|
from common.models import Priority, issue_mail
|
||||||
|
|
||||||
logger = structlog.get_logger('inventree')
|
logger = structlog.get_logger('inventree')
|
||||||
@@ -64,7 +63,7 @@ def is_email_configured() -> bool:
|
|||||||
def send_email(
|
def send_email(
|
||||||
subject: str,
|
subject: str,
|
||||||
body: str,
|
body: str,
|
||||||
recipients: Union[str, list],
|
recipients: str | list,
|
||||||
from_email: Optional[str] = None,
|
from_email: Optional[str] = None,
|
||||||
html_message=None,
|
html_message=None,
|
||||||
prio: Priority = Priority.NORMAL,
|
prio: Priority = Priority.NORMAL,
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
"""Provides helper mixins that are used throughout the InvenTree project."""
|
"""Provides helper mixins that are used throughout the InvenTree project."""
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
|
from collections.abc import Callable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Callable
|
from typing import Any
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
|||||||
@@ -35,10 +35,6 @@ def get_type_str(type_obj):
|
|||||||
if type_obj.__module__ == 'builtins':
|
if type_obj.__module__ == 'builtins':
|
||||||
return type_obj.__name__
|
return type_obj.__name__
|
||||||
|
|
||||||
# in python3.9, typing.Union has no __name__
|
|
||||||
if not hasattr(type_obj, '__module__') or not hasattr(type_obj, '__name__'):
|
|
||||||
return str(type_obj)
|
|
||||||
|
|
||||||
return f'{type_obj.__module__}.{type_obj.__name__}'
|
return f'{type_obj.__module__}.{type_obj.__name__}'
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Extended schema generator."""
|
"""Extended schema generator."""
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TypeVar, Union
|
from typing import TypeVar
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ def prep_name(ref):
|
|||||||
return f'{dja_ref_prefix}.{ref}'
|
return f'{dja_ref_prefix}.{ref}'
|
||||||
|
|
||||||
|
|
||||||
def sub_component_name(name: T) -> Union[T, str]:
|
def sub_component_name(name: T) -> T | str:
|
||||||
"""Clean up component references."""
|
"""Clean up component references."""
|
||||||
if not isinstance(name, str):
|
if not isinstance(name, str):
|
||||||
return name
|
return name
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
"""Generic models which provide extra functionality over base Django model types."""
|
"""Generic models which provide extra functionality over base Django model types."""
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from string import Formatter
|
from string import Formatter
|
||||||
from typing import Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
@@ -19,6 +20,7 @@ from django_q.models import Task
|
|||||||
from error_report.models import Error
|
from error_report.models import Error
|
||||||
from mptt.exceptions import InvalidMove
|
from mptt.exceptions import InvalidMove
|
||||||
from mptt.models import MPTTModel, TreeForeignKey
|
from mptt.models import MPTTModel, TreeForeignKey
|
||||||
|
from stdimage.models import StdImageField
|
||||||
|
|
||||||
import common.settings
|
import common.settings
|
||||||
import InvenTree.exceptions
|
import InvenTree.exceptions
|
||||||
@@ -164,10 +166,14 @@ class MetadataMixin(models.Model):
|
|||||||
|
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, force_insert=False, force_update=False, *args, **kwargs):
|
||||||
"""Save the model instance, and perform validation on the metadata field."""
|
"""Save the model instance, and perform validation on the metadata field."""
|
||||||
self.validate_metadata()
|
self.validate_metadata()
|
||||||
super().save(*args, **kwargs)
|
if len(args) > 0:
|
||||||
|
raise TypeError(
|
||||||
|
'save() takes no positional arguments anymore'
|
||||||
|
) # pragma: no cover
|
||||||
|
super().save(force_insert=force_insert, force_update=force_update, **kwargs)
|
||||||
|
|
||||||
def clean(self, *args, **kwargs):
|
def clean(self, *args, **kwargs):
|
||||||
"""Perform model validation on the metadata field."""
|
"""Perform model validation on the metadata field."""
|
||||||
@@ -1293,3 +1299,55 @@ def after_error_logged(sender, instance: Error, created: bool, **kwargs):
|
|||||||
'link': url,
|
'link': url,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class InvenTreeImageMixin(models.Model):
|
||||||
|
"""A mixin class for adding image functionality to a model class.
|
||||||
|
|
||||||
|
The following fields are added to any model which implements this mixin:
|
||||||
|
|
||||||
|
- image : An image field for storing an image
|
||||||
|
"""
|
||||||
|
|
||||||
|
IMAGE_RENAME: Callable | None = None
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Metaclass options for this mixin.
|
||||||
|
|
||||||
|
Note: abstract must be true, as this is only a mixin, not a separate table
|
||||||
|
"""
|
||||||
|
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
"""Custom init method for InvenTreeImageMixin to ensure IMAGE_RENAME is implemented."""
|
||||||
|
if self.IMAGE_RENAME is None:
|
||||||
|
raise NotImplementedError(
|
||||||
|
'IMAGE_RENAME must be implemented in the model class'
|
||||||
|
)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def rename_image(self, filename):
|
||||||
|
"""Rename the uploaded image file using the IMAGE_RENAME function."""
|
||||||
|
return self.IMAGE_RENAME(filename) # type: ignore
|
||||||
|
|
||||||
|
image = StdImageField(
|
||||||
|
upload_to=rename_image,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
variations={'thumbnail': (128, 128), 'preview': (256, 256)},
|
||||||
|
delete_orphans=False,
|
||||||
|
verbose_name=_('Image'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_image_url(self):
|
||||||
|
"""Return the URL of the image for this object."""
|
||||||
|
if self.image:
|
||||||
|
return InvenTree.helpers.getMediaUrl(self.image)
|
||||||
|
return InvenTree.helpers.getBlankImage()
|
||||||
|
|
||||||
|
def get_thumbnail_url(self) -> str:
|
||||||
|
"""Return the URL of the image thumbnail for this object."""
|
||||||
|
if self.image:
|
||||||
|
return InvenTree.helpers.getMediaUrl(self.image, 'thumbnail')
|
||||||
|
return InvenTree.helpers.getBlankThumbnail()
|
||||||
|
|||||||
@@ -695,7 +695,7 @@ Ref: https://docs.djangoproject.com/en/3.2/ref/settings/#std:setting-OPTIONS
|
|||||||
# connecting to the database server (such as a replica failover) don't sit and
|
# connecting to the database server (such as a replica failover) don't sit and
|
||||||
# wait for possibly an hour or more, just tell the client something went wrong
|
# wait for possibly an hour or more, just tell the client something went wrong
|
||||||
# and let the client retry when they want to.
|
# and let the client retry when they want to.
|
||||||
db_options = db_config.get('OPTIONS', db_config.get('options', None))
|
db_options = db_config.get('OPTIONS', db_config.get('options'))
|
||||||
|
|
||||||
if db_options is None:
|
if db_options is None:
|
||||||
db_options = {}
|
db_options = {}
|
||||||
@@ -1428,9 +1428,7 @@ if SITE_URL:
|
|||||||
GLOBAL_SETTINGS_OVERRIDES['INVENTREE_BASE_URL'] = SITE_URL
|
GLOBAL_SETTINGS_OVERRIDES['INVENTREE_BASE_URL'] = SITE_URL
|
||||||
|
|
||||||
if len(GLOBAL_SETTINGS_OVERRIDES) > 0:
|
if len(GLOBAL_SETTINGS_OVERRIDES) > 0:
|
||||||
logger.info(
|
logger.info('INVE-I1: Global settings overrides: %s', GLOBAL_SETTINGS_OVERRIDES)
|
||||||
'INVE-I1: Global settings overrides: %s', str(GLOBAL_SETTINGS_OVERRIDES)
|
|
||||||
)
|
|
||||||
for key in GLOBAL_SETTINGS_OVERRIDES:
|
for key in GLOBAL_SETTINGS_OVERRIDES:
|
||||||
# Set the global setting
|
# Set the global setting
|
||||||
logger.debug('- Override value for %s = ********', key)
|
logger.debug('- Override value for %s = ********', key)
|
||||||
@@ -1469,9 +1467,9 @@ FLAGS = {
|
|||||||
CUSTOM_FLAGS = get_setting('INVENTREE_FLAGS', 'flags', None, typecast=dict)
|
CUSTOM_FLAGS = get_setting('INVENTREE_FLAGS', 'flags', None, typecast=dict)
|
||||||
if CUSTOM_FLAGS: # pragma: no cover
|
if CUSTOM_FLAGS: # pragma: no cover
|
||||||
if not isinstance(CUSTOM_FLAGS, dict):
|
if not isinstance(CUSTOM_FLAGS, dict):
|
||||||
logger.error('Invalid custom flags, must be valid dict: %s', str(CUSTOM_FLAGS))
|
logger.error('Invalid custom flags, must be valid dict: %s', CUSTOM_FLAGS)
|
||||||
else:
|
else:
|
||||||
logger.info('Custom flags: %s', str(CUSTOM_FLAGS))
|
logger.info('Custom flags: %s', CUSTOM_FLAGS)
|
||||||
FLAGS.update(CUSTOM_FLAGS)
|
FLAGS.update(CUSTOM_FLAGS)
|
||||||
|
|
||||||
# Magic login django-sesame
|
# Magic login django-sesame
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import json
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Callable, Optional
|
from typing import Optional
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
@@ -626,7 +627,7 @@ def update_exchange_rates(force: bool = False):
|
|||||||
except (AppRegistryNotReady, OperationalError, ProgrammingError):
|
except (AppRegistryNotReady, OperationalError, ProgrammingError):
|
||||||
logger.warning('Could not update exchange rates - database not ready')
|
logger.warning('Could not update exchange rates - database not ready')
|
||||||
except Exception as e: # pragma: no cover
|
except Exception as e: # pragma: no cover
|
||||||
logger.exception('Error updating exchange rates: %s', str(type(e)))
|
logger.exception('Error updating exchange rates: %s', type(e))
|
||||||
|
|
||||||
|
|
||||||
@tracer.start_as_current_span('run_backup')
|
@tracer.start_as_current_span('run_backup')
|
||||||
@@ -716,7 +717,7 @@ def check_for_migrations(force: bool = False, reload_registry: bool = True) -> b
|
|||||||
|
|
||||||
# Log open migrations
|
# Log open migrations
|
||||||
for migration in plan:
|
for migration in plan:
|
||||||
logger.info('- %s', str(migration[0]))
|
logger.info('- %s', migration[0])
|
||||||
|
|
||||||
# Set the application to maintenance mode - no access from now on.
|
# Set the application to maintenance mode - no access from now on.
|
||||||
set_maintenance_mode(True)
|
set_maintenance_mode(True)
|
||||||
|
|||||||
@@ -576,7 +576,7 @@ class GeneralApiTests(InvenTreeAPITestCase):
|
|||||||
|
|
||||||
with TemporaryDirectory() as tmp: # type: ignore[no-matching-overload]
|
with TemporaryDirectory() as tmp: # type: ignore[no-matching-overload]
|
||||||
sample_file = Path(tmp, 'temp.txt')
|
sample_file = Path(tmp, 'temp.txt')
|
||||||
sample_file.write_text('abc')
|
sample_file.write_text('abc', 'utf-8')
|
||||||
|
|
||||||
# File is not a json
|
# File is not a json
|
||||||
with self.assertLogs(logger='inventree', level='ERROR') as log:
|
with self.assertLogs(logger='inventree', level='ERROR') as log:
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ class MiddlewareTests(InvenTreeTestCase):
|
|||||||
SITE_URL='https://testserver', CSRF_TRUSTED_ORIGINS=['https://testserver']
|
SITE_URL='https://testserver', CSRF_TRUSTED_ORIGINS=['https://testserver']
|
||||||
):
|
):
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse('web'), HTTP_HOST='otherhost.example.com'
|
reverse('web'), headers={'host': 'otherhost.example.com'}
|
||||||
)
|
)
|
||||||
self.assertContains(response, 'INVE-E7: The visited path', status_code=500)
|
self.assertContains(response, 'INVE-E7: The visited path', status_code=500)
|
||||||
|
|
||||||
@@ -199,7 +199,9 @@ class MiddlewareTests(InvenTreeTestCase):
|
|||||||
SITE_URL='https://testserver:8000',
|
SITE_URL='https://testserver:8000',
|
||||||
CSRF_TRUSTED_ORIGINS=['https://testserver:8000'],
|
CSRF_TRUSTED_ORIGINS=['https://testserver:8000'],
|
||||||
):
|
):
|
||||||
response = self.client.get(reverse('web'), HTTP_HOST='testserver:8008')
|
response = self.client.get(
|
||||||
|
reverse('web'), headers={'host': 'testserver:8008'}
|
||||||
|
)
|
||||||
self.do_positive_test(response)
|
self.do_positive_test(response)
|
||||||
|
|
||||||
# Try again with strict protocol check
|
# Try again with strict protocol check
|
||||||
@@ -208,7 +210,9 @@ class MiddlewareTests(InvenTreeTestCase):
|
|||||||
CSRF_TRUSTED_ORIGINS=['https://testserver:8000'],
|
CSRF_TRUSTED_ORIGINS=['https://testserver:8000'],
|
||||||
SITE_LAX_PROTOCOL_CHECK=False,
|
SITE_LAX_PROTOCOL_CHECK=False,
|
||||||
):
|
):
|
||||||
response = self.client.get(reverse('web'), HTTP_HOST='testserver:8008')
|
response = self.client.get(
|
||||||
|
reverse('web'), headers={'host': 'testserver:8008'}
|
||||||
|
)
|
||||||
self.assertContains(response, 'INVE-E7: The visited path', status_code=500)
|
self.assertContains(response, 'INVE-E7: The visited path', status_code=500)
|
||||||
|
|
||||||
def test_site_url_checks_multi(self):
|
def test_site_url_checks_multi(self):
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ from djmoney.contrib.exchange.models import Rate, convert_money
|
|||||||
from djmoney.money import Money
|
from djmoney.money import Money
|
||||||
from maintenance_mode.core import get_maintenance_mode, set_maintenance_mode
|
from maintenance_mode.core import get_maintenance_mode, set_maintenance_mode
|
||||||
from sesame.utils import get_user
|
from sesame.utils import get_user
|
||||||
|
from stdimage.models import StdImageFieldFile
|
||||||
|
|
||||||
import InvenTree.conversion
|
import InvenTree.conversion
|
||||||
import InvenTree.format
|
import InvenTree.format
|
||||||
@@ -700,7 +701,16 @@ class TestHelpers(TestCase):
|
|||||||
|
|
||||||
def testMediaUrl(self):
|
def testMediaUrl(self):
|
||||||
"""Test getMediaUrl."""
|
"""Test getMediaUrl."""
|
||||||
self.assertEqual(helpers.getMediaUrl('xx/yy.png'), '/media/xx/yy.png')
|
# Str should not work
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
helpers.getMediaUrl('xx/yy.png') # type: ignore
|
||||||
|
|
||||||
|
# Correct usage
|
||||||
|
part = Part().image
|
||||||
|
self.assertEqual(
|
||||||
|
helpers.getMediaUrl(StdImageFieldFile(part, part, 'xx/yy.png')), # type: ignore
|
||||||
|
'/media/xx/yy.png',
|
||||||
|
)
|
||||||
|
|
||||||
def testDecimal2String(self):
|
def testDecimal2String(self):
|
||||||
"""Test decimal2string."""
|
"""Test decimal2string."""
|
||||||
@@ -994,12 +1004,14 @@ class TestSerialNumberExtraction(TestCase):
|
|||||||
# Extract a range of values with a smaller range
|
# Extract a range of values with a smaller range
|
||||||
with self.assertRaises(ValidationError) as exc:
|
with self.assertRaises(ValidationError) as exc:
|
||||||
e('11-50', 10, 1)
|
e('11-50', 10, 1)
|
||||||
self.assertIn('Range quantity exceeds 10', str(exc))
|
self.assertIn(
|
||||||
|
'Group range 11-50 exceeds allowed quantity (10)', str(exc.exception)
|
||||||
|
)
|
||||||
|
|
||||||
# Test groups are not interpolated with alpha characters
|
# Test groups are not interpolated with alpha characters
|
||||||
with self.assertRaises(ValidationError) as exc:
|
with self.assertRaises(ValidationError) as exc:
|
||||||
e('1, A-2, 3+', 5, 1)
|
e('1, A-2, 3+', 5, 1)
|
||||||
self.assertIn('Invalid group range: A-2', str(exc))
|
self.assertIn('Invalid group: A-2', str(exc.exception))
|
||||||
|
|
||||||
def test_combinations(self):
|
def test_combinations(self):
|
||||||
"""Test complex serial number combinations."""
|
"""Test complex serial number combinations."""
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import json
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
from collections.abc import Callable
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable, Optional, Union
|
from typing import Optional
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
@@ -731,7 +732,7 @@ class InvenTreeAPITestCase(
|
|||||||
def run_output_test(
|
def run_output_test(
|
||||||
self,
|
self,
|
||||||
url: str,
|
url: str,
|
||||||
test_cases: list[Union[tuple[str, str], str]],
|
test_cases: list[tuple[str, str] | str],
|
||||||
additional_params: Optional[dict] = None,
|
additional_params: Optional[dict] = None,
|
||||||
assert_subset: bool = False,
|
assert_subset: bool = False,
|
||||||
assert_fnc: Optional[Callable] = None,
|
assert_fnc: Optional[Callable] = None,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from django.core import validators
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
import pint
|
|
||||||
import pint.errors
|
import pint.errors
|
||||||
from moneyed import CURRENCIES
|
from moneyed import CURRENCIES
|
||||||
|
|
||||||
|
|||||||
@@ -59,20 +59,20 @@ except Exception as exc:
|
|||||||
|
|
||||||
|
|
||||||
def checkMinPythonVersion():
|
def checkMinPythonVersion():
|
||||||
"""Check that the Python version is at least 3.9."""
|
"""Check that the Python version is at least 3.11."""
|
||||||
version = sys.version.split(' ')[0]
|
version = sys.version.split(' ')[0]
|
||||||
docs = 'https://docs.inventree.org/en/stable/start/intro/#python-requirements'
|
docs = 'https://docs.inventree.org/en/stable/start/intro/#python-requirements'
|
||||||
|
|
||||||
msg = f"""
|
msg = f"""
|
||||||
InvenTree requires Python 3.9 or above - you are running version {version}.
|
InvenTree requires Python 3.11 or above - you are running version {version}.
|
||||||
- Refer to the InvenTree documentation for more information:
|
- Refer to the InvenTree documentation for more information:
|
||||||
- {docs}
|
- {docs}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if sys.version_info.major < 3:
|
if sys.version_info.major < 3: # noqa: UP036
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
if sys.version_info.major == 3 and sys.version_info.minor < 9:
|
if sys.version_info.major == 3 and sys.version_info.minor < 11:
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
print(f'Python version {version} - {sys.executable}')
|
print(f'Python version {version} - {sys.executable}')
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db.models import F, Q
|
from django.db.models import F, Q
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
@@ -629,7 +627,7 @@ class BuildLineList(
|
|||||||
'bom_item__reference',
|
'bom_item__reference',
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_source_build(self) -> Optional[Build]:
|
def get_source_build(self) -> Build | None:
|
||||||
"""Return the target build for the BuildLine queryset."""
|
"""Return the target build for the BuildLine queryset."""
|
||||||
source_build = None
|
source_build = None
|
||||||
|
|
||||||
@@ -648,7 +646,7 @@ class BuildLineDetail(BuildLineMixin, OutputOptionsMixin, RetrieveUpdateDestroyA
|
|||||||
|
|
||||||
output_options = BuildLineOutputOptions
|
output_options = BuildLineOutputOptions
|
||||||
|
|
||||||
def get_source_build(self) -> Optional[Build]:
|
def get_source_build(self) -> Build | None:
|
||||||
"""Return the target source location for the BuildLine queryset."""
|
"""Return the target source location for the BuildLine queryset."""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -1640,7 +1640,9 @@ class BuildConsumeTest(BuildAPITest):
|
|||||||
data = {
|
data = {
|
||||||
'items': [
|
'items': [
|
||||||
{'build_line': line.pk, 'stock_item': si.pk, 'quantity': 100}
|
{'build_line': line.pk, 'stock_item': si.pk, 'quantity': 100}
|
||||||
for line, si in zip(self.build.build_lines.all(), self.stock_items)
|
for line, si in zip(
|
||||||
|
self.build.build_lines.all(), self.stock_items, strict=False
|
||||||
|
)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from email.utils import make_msgid
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from secrets import compare_digest
|
from secrets import compare_digest
|
||||||
from typing import Any, Optional, Union
|
from typing import Any, Optional
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings as django_settings
|
from django.conf import settings as django_settings
|
||||||
@@ -67,7 +67,7 @@ from InvenTree.version import inventree_identifier
|
|||||||
logger = structlog.get_logger('inventree')
|
logger = structlog.get_logger('inventree')
|
||||||
|
|
||||||
|
|
||||||
class RenderMeta(enums.ChoicesMeta):
|
class RenderMeta(enums.ChoicesType):
|
||||||
"""Metaclass for rendering choices."""
|
"""Metaclass for rendering choices."""
|
||||||
|
|
||||||
choice_fnc = None
|
choice_fnc = None
|
||||||
@@ -239,9 +239,7 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
missing_keys = set(settings_keys) - set(existing_keys)
|
missing_keys = set(settings_keys) - set(existing_keys)
|
||||||
|
|
||||||
if len(missing_keys) > 0:
|
if len(missing_keys) > 0:
|
||||||
logger.info(
|
logger.info('Building %s default values for %s', len(missing_keys), cls)
|
||||||
'Building %s default values for %s', len(missing_keys), str(cls)
|
|
||||||
)
|
|
||||||
cls.objects.bulk_create([
|
cls.objects.bulk_create([
|
||||||
cls(key=key, value=cls.get_setting_default(key), **kwargs)
|
cls(key=key, value=cls.get_setting_default(key), **kwargs)
|
||||||
for key in missing_keys
|
for key in missing_keys
|
||||||
@@ -249,7 +247,7 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
])
|
])
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
'Failed to build default values for %s (%s)', str(cls), str(type(exc))
|
'Failed to build default values for %s (%s)', cls, type(exc)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _call_settings_function(self, reference: str, args, kwargs):
|
def _call_settings_function(self, reference: str, args, kwargs):
|
||||||
@@ -330,7 +328,7 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
cls,
|
cls,
|
||||||
*,
|
*,
|
||||||
exclude_hidden=False,
|
exclude_hidden=False,
|
||||||
settings_definition: Union[dict[str, SettingsKeyType], None] = None,
|
settings_definition: dict[str, SettingsKeyType] | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""Return a list of "all" defined settings.
|
"""Return a list of "all" defined settings.
|
||||||
@@ -392,7 +390,7 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
cls,
|
cls,
|
||||||
*,
|
*,
|
||||||
exclude_hidden=False,
|
exclude_hidden=False,
|
||||||
settings_definition: Union[dict[str, SettingsKeyType], None] = None,
|
settings_definition: dict[str, SettingsKeyType] | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""Return a dict of "all" defined global settings.
|
"""Return a dict of "all" defined global settings.
|
||||||
@@ -419,7 +417,7 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
cls,
|
cls,
|
||||||
*,
|
*,
|
||||||
exclude_hidden=False,
|
exclude_hidden=False,
|
||||||
settings_definition: Union[dict[str, SettingsKeyType], None] = None,
|
settings_definition: dict[str, SettingsKeyType] | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""Check if all required settings are set by definition.
|
"""Check if all required settings are set by definition.
|
||||||
@@ -647,9 +645,7 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
and not key.startswith('_')
|
and not key.startswith('_')
|
||||||
):
|
):
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"get_setting: Setting key '%s' is not defined for class %s",
|
"get_setting: Setting key '%s' is not defined for class %s", key, cls
|
||||||
key,
|
|
||||||
str(cls),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# If no backup value is specified, attempt to retrieve a "default" value
|
# If no backup value is specified, attempt to retrieve a "default" value
|
||||||
@@ -693,9 +689,7 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
and not key.startswith('_')
|
and not key.startswith('_')
|
||||||
):
|
):
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"set_setting: Setting key '%s' is not defined for class %s",
|
"set_setting: Setting key '%s' is not defined for class %s", key, cls
|
||||||
key,
|
|
||||||
str(cls),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if change_user is not None and not change_user.is_staff:
|
if change_user is not None and not change_user.is_staff:
|
||||||
@@ -734,7 +728,7 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
return
|
return
|
||||||
except Exception as exc: # pragma: no cover
|
except Exception as exc: # pragma: no cover
|
||||||
logger.exception(
|
logger.exception(
|
||||||
"Error setting setting '%s' for %s: %s", key, str(cls), str(type(exc))
|
"Error setting setting '%s' for %s: %s", key, cls, type(exc)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -753,7 +747,7 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
if attempts > 0:
|
if attempts > 0:
|
||||||
# Try again
|
# Try again
|
||||||
logger.info(
|
logger.info(
|
||||||
"Duplicate setting key '%s' for %s - trying again", key, str(cls)
|
"Duplicate setting key '%s' for %s - trying again", key, cls
|
||||||
)
|
)
|
||||||
cls.set_setting(
|
cls.set_setting(
|
||||||
key,
|
key,
|
||||||
@@ -770,7 +764,7 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
except Exception as exc: # pragma: no cover
|
except Exception as exc: # pragma: no cover
|
||||||
# Some other error
|
# Some other error
|
||||||
logger.exception(
|
logger.exception(
|
||||||
"Error setting setting '%s' for %s: %s", key, str(cls), str(type(exc))
|
"Error setting setting '%s' for %s: %s", key, cls, type(exc)
|
||||||
)
|
)
|
||||||
|
|
||||||
key = models.CharField(
|
key = models.CharField(
|
||||||
@@ -2659,7 +2653,7 @@ class EmailMessage(models.Model):
|
|||||||
direction = models.CharField(
|
direction = models.CharField(
|
||||||
max_length=50, blank=True, null=True, choices=EmailDirection.choices
|
max_length=50, blank=True, null=True, choices=EmailDirection.choices
|
||||||
)
|
)
|
||||||
priority = models.IntegerField(verbose_name=_('Prioriy'), choices=Priority.choices)
|
priority = models.IntegerField(verbose_name=_('Prioriy'), choices=Priority)
|
||||||
delivery_options = models.JSONField(
|
delivery_options = models.JSONField(
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
@@ -2741,7 +2735,7 @@ def issue_mail(
|
|||||||
subject: str,
|
subject: str,
|
||||||
body: str,
|
body: str,
|
||||||
from_email: str,
|
from_email: str,
|
||||||
recipients: Union[str, list],
|
recipients: str | list,
|
||||||
fail_silently: bool = False,
|
fail_silently: bool = False,
|
||||||
html_message=None,
|
html_message=None,
|
||||||
prio: Priority = Priority.NORMAL,
|
prio: Priority = Priority.NORMAL,
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ def trigger_notification(obj: Model, category: str = '', obj_ref: str = 'pk', **
|
|||||||
logger.info(
|
logger.info(
|
||||||
"Notification '%s' has recently been sent for '%s' - SKIPPING",
|
"Notification '%s' has recently been sent for '%s' - SKIPPING",
|
||||||
category,
|
category,
|
||||||
str(obj),
|
obj,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ from jinja2 import Template
|
|||||||
|
|
||||||
import build.validators
|
import build.validators
|
||||||
import common.currency
|
import common.currency
|
||||||
import common.models
|
|
||||||
import common.validators
|
import common.validators
|
||||||
import order.validators
|
import order.validators
|
||||||
import report.helpers
|
import report.helpers
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"""Types for settings."""
|
"""Types for settings."""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Callable, TypedDict, Union
|
from collections.abc import Callable
|
||||||
|
from typing import Any, TypedDict
|
||||||
|
|
||||||
if sys.version_info >= (3, 11):
|
if sys.version_info >= (3, 11):
|
||||||
from typing import NotRequired # pragma: no cover
|
from typing import NotRequired # pragma: no cover
|
||||||
@@ -36,9 +37,9 @@ class SettingsKeyType(TypedDict, total=False):
|
|||||||
name: str
|
name: str
|
||||||
description: str
|
description: str
|
||||||
units: str
|
units: str
|
||||||
validator: Union[Callable, list[Callable], tuple[Callable]]
|
validator: Callable | list[Callable] | tuple[Callable]
|
||||||
default: Union[Callable, Any]
|
default: Callable | Any
|
||||||
choices: Union[list[tuple[str, str]], Callable[[], list[tuple[str, str]]]]
|
choices: list[tuple[str, str]] | Callable[[], list[tuple[str, str]]]
|
||||||
hidden: bool
|
hidden: bool
|
||||||
before_save: Callable[..., None]
|
before_save: Callable[..., None]
|
||||||
after_save: Callable[..., None]
|
after_save: Callable[..., None]
|
||||||
|
|||||||
@@ -1105,7 +1105,7 @@ class WebhookMessageTests(TestCase):
|
|||||||
def test_bad_token(self):
|
def test_bad_token(self):
|
||||||
"""Test that a wrong token is not working."""
|
"""Test that a wrong token is not working."""
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
self.url, content_type=CONTENT_TYPE_JSON, HTTP_TOKEN='1234567fghj'
|
self.url, content_type=CONTENT_TYPE_JSON, headers={'token': '1234567fghj'}
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == HTTPStatus.FORBIDDEN
|
assert response.status_code == HTTPStatus.FORBIDDEN
|
||||||
@@ -1128,7 +1128,7 @@ class WebhookMessageTests(TestCase):
|
|||||||
self.url,
|
self.url,
|
||||||
data="{'this': 123}",
|
data="{'this': 123}",
|
||||||
content_type=CONTENT_TYPE_JSON,
|
content_type=CONTENT_TYPE_JSON,
|
||||||
HTTP_TOKEN=str(self.endpoint_def.token),
|
headers={'token': str(self.endpoint_def.token)},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == HTTPStatus.NOT_ACCEPTABLE
|
assert response.status_code == HTTPStatus.NOT_ACCEPTABLE
|
||||||
@@ -1176,7 +1176,7 @@ class WebhookMessageTests(TestCase):
|
|||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
self.url,
|
self.url,
|
||||||
content_type=CONTENT_TYPE_JSON,
|
content_type=CONTENT_TYPE_JSON,
|
||||||
HTTP_TOKEN='68MXtc/OiXdA5e2Nq9hATEVrZFpLb3Zb0oau7n8s31I=',
|
headers={'token': '68MXtc/OiXdA5e2Nq9hATEVrZFpLb3Zb0oau7n8s31I='},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == HTTPStatus.OK
|
assert response.status_code == HTTPStatus.OK
|
||||||
@@ -1191,7 +1191,7 @@ class WebhookMessageTests(TestCase):
|
|||||||
self.url,
|
self.url,
|
||||||
data={'this': 'is a message'},
|
data={'this': 'is a message'},
|
||||||
content_type=CONTENT_TYPE_JSON,
|
content_type=CONTENT_TYPE_JSON,
|
||||||
HTTP_TOKEN=str(self.endpoint_def.token),
|
headers={'token': str(self.endpoint_def.token)},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == HTTPStatus.OK
|
assert response.status_code == HTTPStatus.OK
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"""Validation helpers for common models."""
|
"""Validation helpers for common models."""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@@ -107,7 +106,7 @@ def validate_email_domains(setting):
|
|||||||
raise ValidationError(_(f'Invalid domain name: {domain}'))
|
raise ValidationError(_(f'Invalid domain name: {domain}'))
|
||||||
|
|
||||||
|
|
||||||
def validate_icon(name: Union[str, None]):
|
def validate_icon(name: str | None):
|
||||||
"""Validate the provided icon name, and ignore if empty."""
|
"""Validate the provided icon name, and ignore if empty."""
|
||||||
if name == '' or name is None:
|
if name == '' or name is None:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-04 20:59
|
||||||
|
|
||||||
|
import InvenTree.models
|
||||||
|
import stdimage.models
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('company', '0075_company_tax_id'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='company',
|
||||||
|
name='image',
|
||||||
|
field=stdimage.models.StdImageField(blank=True, force_min_size=False, null=True, upload_to=InvenTree.models.InvenTreeImageMixin.rename_image, variations={'preview': (256, 256), 'thumbnail': (128, 128)}, verbose_name='Image'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -16,7 +16,6 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from django.utils.translation import pgettext_lazy as __
|
from django.utils.translation import pgettext_lazy as __
|
||||||
|
|
||||||
from moneyed import CURRENCIES
|
from moneyed import CURRENCIES
|
||||||
from stdimage.models import StdImageField
|
|
||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
import common.currency
|
import common.currency
|
||||||
@@ -80,6 +79,7 @@ class Company(
|
|||||||
InvenTree.models.InvenTreeAttachmentMixin,
|
InvenTree.models.InvenTreeAttachmentMixin,
|
||||||
InvenTree.models.InvenTreeNotesMixin,
|
InvenTree.models.InvenTreeNotesMixin,
|
||||||
report.mixins.InvenTreeReportMixin,
|
report.mixins.InvenTreeReportMixin,
|
||||||
|
InvenTree.models.InvenTreeImageMixin,
|
||||||
InvenTree.models.InvenTreeMetadataModel,
|
InvenTree.models.InvenTreeMetadataModel,
|
||||||
):
|
):
|
||||||
"""A Company object represents an external company.
|
"""A Company object represents an external company.
|
||||||
@@ -109,6 +109,8 @@ class Company(
|
|||||||
tax_id: Tax ID for the company
|
tax_id: Tax ID for the company
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
IMAGE_RENAME = rename_company_image
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Metaclass defines extra model options."""
|
"""Metaclass defines extra model options."""
|
||||||
|
|
||||||
@@ -185,15 +187,6 @@ class Company(
|
|||||||
max_length=2000,
|
max_length=2000,
|
||||||
)
|
)
|
||||||
|
|
||||||
image = StdImageField(
|
|
||||||
upload_to=rename_company_image,
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
variations={'thumbnail': (128, 128), 'preview': (256, 256)},
|
|
||||||
delete_orphans=True,
|
|
||||||
verbose_name=_('Image'),
|
|
||||||
)
|
|
||||||
|
|
||||||
active = models.BooleanField(
|
active = models.BooleanField(
|
||||||
default=True, verbose_name=_('Active'), help_text=_('Is this company active?')
|
default=True, verbose_name=_('Active'), help_text=_('Is this company active?')
|
||||||
)
|
)
|
||||||
@@ -269,18 +262,6 @@ class Company(
|
|||||||
"""Get the web URL for the detail view for this Company."""
|
"""Get the web URL for the detail view for this Company."""
|
||||||
return InvenTree.helpers.pui_url(f'/purchasing/manufacturer/{self.id}')
|
return InvenTree.helpers.pui_url(f'/purchasing/manufacturer/{self.id}')
|
||||||
|
|
||||||
def get_image_url(self):
|
|
||||||
"""Return the URL of the image for this company."""
|
|
||||||
if self.image:
|
|
||||||
return InvenTree.helpers.getMediaUrl(self.image.url)
|
|
||||||
return InvenTree.helpers.getBlankImage()
|
|
||||||
|
|
||||||
def get_thumbnail_url(self):
|
|
||||||
"""Return the URL for the thumbnail image for this Company."""
|
|
||||||
if self.image:
|
|
||||||
return InvenTree.helpers.getMediaUrl(self.image.thumbnail.url)
|
|
||||||
return InvenTree.helpers.getBlankThumbnail()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parts(self):
|
def parts(self):
|
||||||
"""Return SupplierPart objects which are supplied or manufactured by this company."""
|
"""Return SupplierPart objects which are supplied or manufactured by this company."""
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Classes and functions for plugin controlled object state transitions."""
|
"""Classes and functions for plugin controlled object state transitions."""
|
||||||
|
|
||||||
from typing import Callable
|
from collections.abc import Callable
|
||||||
|
|
||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
|
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ class DataImportSession(models.Model):
|
|||||||
|
|
||||||
# Iterate through each "row" in the data file, and create a new DataImportRow object
|
# Iterate through each "row" in the data file, and create a new DataImportRow object
|
||||||
for idx, row in enumerate(df):
|
for idx, row in enumerate(df):
|
||||||
row_data = dict(zip(headers, row))
|
row_data = dict(zip(headers, row, strict=False))
|
||||||
|
|
||||||
# Skip completely empty rows
|
# Skip completely empty rows
|
||||||
if not any(row_data.values()):
|
if not any(row_data.values()):
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ def import_data(session_id: int):
|
|||||||
Attempt to load data from the provided file, and potentially handle any errors.
|
Attempt to load data from the provided file, and potentially handle any errors.
|
||||||
"""
|
"""
|
||||||
import importer.models
|
import importer.models
|
||||||
import importer.operations
|
|
||||||
import importer.status_codes
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
session = importer.models.DataImportSession.objects.get(pk=session_id)
|
session = importer.models.DataImportSession.objects.get(pk=session_id)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Base machine type/base driver."""
|
"""Base machine type/base driver."""
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Any, Literal, TypedDict, Union
|
from typing import TYPE_CHECKING, Any, Literal, TypedDict
|
||||||
|
|
||||||
from generic.states import StatusCode
|
from generic.states import StatusCode
|
||||||
from InvenTree.helpers_mixin import (
|
from InvenTree.helpers_mixin import (
|
||||||
@@ -63,10 +63,10 @@ class MachineProperty(TypedDict, total=False):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
key: str
|
key: str
|
||||||
value: Union[str, bool, int, float]
|
value: str | bool | int | float
|
||||||
group: str
|
group: str
|
||||||
type: MachinePropertyType
|
type: MachinePropertyType
|
||||||
max_progress: Union[int, None]
|
max_progress: int | None
|
||||||
|
|
||||||
|
|
||||||
class BaseDriver(
|
class BaseDriver(
|
||||||
@@ -160,7 +160,7 @@ class BaseDriver(
|
|||||||
|
|
||||||
return registry.get_machines(driver=self, **kwargs)
|
return registry.get_machines(driver=self, **kwargs)
|
||||||
|
|
||||||
def handle_error(self, error: Union[Exception, str]):
|
def handle_error(self, error: Exception | str):
|
||||||
"""Handle driver error.
|
"""Handle driver error.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -171,7 +171,7 @@ class BaseDriver(
|
|||||||
|
|
||||||
# --- state getters/setters
|
# --- state getters/setters
|
||||||
@property
|
@property
|
||||||
def errors(self) -> list[Union[str, Exception]]:
|
def errors(self) -> list[str | Exception]:
|
||||||
"""List of driver errors."""
|
"""List of driver errors."""
|
||||||
return self.get_shared_state('errors', [])
|
return self.get_shared_state('errors', [])
|
||||||
|
|
||||||
@@ -343,7 +343,7 @@ class BaseMachineType(
|
|||||||
self.handle_error(e)
|
self.handle_error(e)
|
||||||
|
|
||||||
# --- helper functions
|
# --- helper functions
|
||||||
def handle_error(self, error: Union[Exception, str]):
|
def handle_error(self, error: Exception | str):
|
||||||
"""Helper function for capturing errors with the machine.
|
"""Helper function for capturing errors with the machine.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -475,7 +475,7 @@ class BaseMachineType(
|
|||||||
self.set_shared_state('restart_required', value)
|
self.set_shared_state('restart_required', value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def errors(self) -> list[Union[str, Exception]]:
|
def errors(self) -> list[str | Exception]:
|
||||||
"""List of machine errors."""
|
"""List of machine errors."""
|
||||||
return self.get_shared_state('errors', [])
|
return self.get_shared_state('errors', [])
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Label printing machine type."""
|
"""Label printing machine type."""
|
||||||
|
|
||||||
from typing import Union, cast
|
from typing import cast
|
||||||
|
|
||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.db import models
|
from django.db import models
|
||||||
@@ -59,7 +59,7 @@ class LabelPrinterBaseDriver(BaseDriver):
|
|||||||
label: LabelTemplate,
|
label: LabelTemplate,
|
||||||
items: QuerySet[models.Model],
|
items: QuerySet[models.Model],
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> Union[JsonResponse, None]:
|
) -> JsonResponse | None:
|
||||||
"""Print one or more labels with the provided template and items.
|
"""Print one or more labels with the provided template and items.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -155,7 +155,7 @@ class LabelPrinterBaseDriver(BaseDriver):
|
|||||||
|
|
||||||
def render_to_png(
|
def render_to_png(
|
||||||
self, label: LabelTemplate, item: models.Model, **kwargs
|
self, label: LabelTemplate, item: models.Model, **kwargs
|
||||||
) -> Union[Image, None]:
|
) -> Image | None:
|
||||||
"""Helper method to render a label to PNG format for a specific item.
|
"""Helper method to render a label to PNG format for a specific item.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Machine registry."""
|
"""Machine registry."""
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
from typing import Any, Optional, Union, cast
|
from typing import Any, Optional, cast
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from django.db.utils import IntegrityError, OperationalError, ProgrammingError
|
from django.db.utils import IntegrityError, OperationalError, ProgrammingError
|
||||||
@@ -114,16 +114,16 @@ class MachineRegistry(
|
|||||||
self._hash = None
|
self._hash = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def errors(self) -> list[Union[str, Exception]]:
|
def errors(self) -> list[str | Exception]:
|
||||||
"""List of registry errors."""
|
"""List of registry errors."""
|
||||||
return cast(list[Union[str, Exception]], self.get_shared_state('errors', []))
|
return cast(list[str | Exception], self.get_shared_state('errors', []))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_ready(self) -> bool:
|
def is_ready(self) -> bool:
|
||||||
"""Check if the machine registry is ready."""
|
"""Check if the machine registry is ready."""
|
||||||
return self.ready
|
return self.ready
|
||||||
|
|
||||||
def handle_error(self, error: Union[Exception, str]):
|
def handle_error(self, error: Exception | str):
|
||||||
"""Helper function for capturing errors with the machine registry."""
|
"""Helper function for capturing errors with the machine registry."""
|
||||||
if error not in self.errors:
|
if error not in self.errors:
|
||||||
self.set_shared_state('errors', [*self.errors, error])
|
self.set_shared_state('errors', [*self.errors, error])
|
||||||
@@ -384,7 +384,7 @@ class MachineRegistry(
|
|||||||
return list(self.machine_types.values())
|
return list(self.machine_types.values())
|
||||||
|
|
||||||
@machine_registry_entrypoint()
|
@machine_registry_entrypoint()
|
||||||
def get_machine(self, pk: Union[str, UUID]) -> Optional[BaseMachineType]:
|
def get_machine(self, pk: str | UUID) -> Optional[BaseMachineType]:
|
||||||
"""Get machine from registry by pk."""
|
"""Get machine from registry by pk."""
|
||||||
return self.machines.get(str(pk), None)
|
return self.machines.get(str(pk), None)
|
||||||
|
|
||||||
@@ -454,7 +454,7 @@ class MachineRegistry(
|
|||||||
try:
|
try:
|
||||||
reg_hash = get_global_setting('_MACHINE_REGISTRY_HASH', '', create=False)
|
reg_hash = get_global_setting('_MACHINE_REGISTRY_HASH', '', create=False)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception('Failed to get machine registry hash: %s', str(exc))
|
logger.exception('Failed to get machine registry hash: %s', exc)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if reg_hash and reg_hash != self._hash:
|
if reg_hash and reg_hash != self._hash:
|
||||||
@@ -480,12 +480,12 @@ class MachineRegistry(
|
|||||||
|
|
||||||
if old_hash != self._hash:
|
if old_hash != self._hash:
|
||||||
try:
|
try:
|
||||||
logger.info('Updating machine registry hash: %s', str(self._hash))
|
logger.info('Updating machine registry hash: %s', self._hash)
|
||||||
set_global_setting('_MACHINE_REGISTRY_HASH', self._hash)
|
set_global_setting('_MACHINE_REGISTRY_HASH', self._hash)
|
||||||
except (IntegrityError, OperationalError, ProgrammingError):
|
except (IntegrityError, OperationalError, ProgrammingError):
|
||||||
pass
|
pass
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception('Failed to update machine registry hash: %s', str(exc))
|
logger.exception('Failed to update machine registry hash: %s', exc)
|
||||||
|
|
||||||
@machine_registry_entrypoint()
|
@machine_registry_entrypoint()
|
||||||
def call_machine_function(
|
def call_machine_function(
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
"""Serializers for the machine app."""
|
"""Serializers for the machine app."""
|
||||||
|
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
@@ -96,7 +94,7 @@ class MachineConfigSerializer(serializers.ModelSerializer):
|
|||||||
return status.value
|
return status.value
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
def get_status_model(self, obj: MachineConfig) -> Union[str, None]:
|
def get_status_model(self, obj: MachineConfig) -> str | None:
|
||||||
"""Textual machine status name if available, else None."""
|
"""Textual machine status name if available, else None."""
|
||||||
if obj.machine and obj.machine.MACHINE_STATUS:
|
if obj.machine and obj.machine.MACHINE_STATUS:
|
||||||
return obj.machine.MACHINE_STATUS.__name__
|
return obj.machine.MACHINE_STATUS.__name__
|
||||||
@@ -171,7 +169,7 @@ class BaseMachineClassSerializer(serializers.Serializer):
|
|||||||
"""File that contains the class definition."""
|
"""File that contains the class definition."""
|
||||||
return obj.get_provider_file()
|
return obj.get_provider_file()
|
||||||
|
|
||||||
def get_provider_plugin(self, obj: ClassProviderMixin) -> Union[dict, None]:
|
def get_provider_plugin(self, obj: ClassProviderMixin) -> dict | None:
|
||||||
"""Plugin(s) that contain(s) the class definition."""
|
"""Plugin(s) that contain(s) the class definition."""
|
||||||
plugin = obj.get_provider_plugin()
|
plugin = obj.get_provider_plugin()
|
||||||
if plugin:
|
if plugin:
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"""Background tasks for the 'order' app."""
|
"""Background tasks for the 'order' app."""
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from django.contrib.auth.models import Group, User
|
from django.contrib.auth.models import Group, User
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
@@ -105,7 +104,7 @@ def check_overdue_purchase_orders():
|
|||||||
@tracer.start_as_current_span('notify_overdue_sales_order')
|
@tracer.start_as_current_span('notify_overdue_sales_order')
|
||||||
def notify_overdue_sales_order(so: order.models.SalesOrder) -> None:
|
def notify_overdue_sales_order(so: order.models.SalesOrder) -> None:
|
||||||
"""Notify appropriate users that a SalesOrder has just become 'overdue'."""
|
"""Notify appropriate users that a SalesOrder has just become 'overdue'."""
|
||||||
targets: list[Union[User, Group, Owner]] = []
|
targets: list[User | Group | Owner] = []
|
||||||
|
|
||||||
if so.created_by:
|
if so.created_by:
|
||||||
targets.append(so.created_by)
|
targets.append(so.created_by)
|
||||||
@@ -172,7 +171,7 @@ def check_overdue_sales_orders():
|
|||||||
@tracer.start_as_current_span('notify_overdue_return_order')
|
@tracer.start_as_current_span('notify_overdue_return_order')
|
||||||
def notify_overdue_return_order(ro: order.models.ReturnOrder) -> None:
|
def notify_overdue_return_order(ro: order.models.ReturnOrder) -> None:
|
||||||
"""Notify appropriate users that a ReturnOrder has just become 'overdue'."""
|
"""Notify appropriate users that a ReturnOrder has just become 'overdue'."""
|
||||||
targets: list[Union[User, Group, Owner]] = []
|
targets: list[User | Group | Owner] = []
|
||||||
|
|
||||||
if ro.created_by:
|
if ro.created_by:
|
||||||
targets.append(ro.created_by)
|
targets.append(ro.created_by)
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-04 20:59
|
||||||
|
|
||||||
|
import InvenTree.models
|
||||||
|
import stdimage.models
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('part', '0142_remove_part_last_stocktake_remove_partstocktake_note_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='part',
|
||||||
|
name='image',
|
||||||
|
field=stdimage.models.StdImageField(blank=True, force_min_size=False, null=True, upload_to=InvenTree.models.InvenTreeImageMixin.rename_image, variations={'preview': (256, 256), 'thumbnail': (128, 128)}, verbose_name='Image'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -10,7 +10,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from decimal import Decimal, InvalidOperation
|
from decimal import Decimal, InvalidOperation
|
||||||
from typing import Optional, cast
|
from typing import cast
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
@@ -36,12 +36,10 @@ from djmoney.contrib.exchange.models import convert_money
|
|||||||
from djmoney.money import Money
|
from djmoney.money import Money
|
||||||
from mptt.managers import TreeManager
|
from mptt.managers import TreeManager
|
||||||
from mptt.models import TreeForeignKey
|
from mptt.models import TreeForeignKey
|
||||||
from stdimage.models import StdImageField
|
|
||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
import common.currency
|
import common.currency
|
||||||
import common.models
|
import common.models
|
||||||
import common.settings
|
|
||||||
import InvenTree.conversion
|
import InvenTree.conversion
|
||||||
import InvenTree.fields
|
import InvenTree.fields
|
||||||
import InvenTree.helpers
|
import InvenTree.helpers
|
||||||
@@ -394,15 +392,15 @@ class PartReportContext(report.mixins.BaseReportContext):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
bom_items: report.mixins.QuerySet[BomItem]
|
bom_items: report.mixins.QuerySet[BomItem]
|
||||||
category: Optional[PartCategory]
|
category: PartCategory | None
|
||||||
description: str
|
description: str
|
||||||
IPN: Optional[str]
|
IPN: str | None
|
||||||
name: str
|
name: str
|
||||||
parameters: dict[str, str]
|
parameters: dict[str, str]
|
||||||
part: Part
|
part: Part
|
||||||
qr_data: str
|
qr_data: str
|
||||||
qr_url: str
|
qr_url: str
|
||||||
revision: Optional[str]
|
revision: str | None
|
||||||
test_template_list: report.mixins.QuerySet[PartTestTemplate]
|
test_template_list: report.mixins.QuerySet[PartTestTemplate]
|
||||||
test_templates: dict[str, PartTestTemplate]
|
test_templates: dict[str, PartTestTemplate]
|
||||||
|
|
||||||
@@ -414,6 +412,7 @@ class Part(
|
|||||||
InvenTree.models.InvenTreeBarcodeMixin,
|
InvenTree.models.InvenTreeBarcodeMixin,
|
||||||
InvenTree.models.InvenTreeNotesMixin,
|
InvenTree.models.InvenTreeNotesMixin,
|
||||||
report.mixins.InvenTreeReportMixin,
|
report.mixins.InvenTreeReportMixin,
|
||||||
|
InvenTree.models.InvenTreeImageMixin,
|
||||||
InvenTree.models.MetadataMixin,
|
InvenTree.models.MetadataMixin,
|
||||||
InvenTree.models.InvenTreeTree,
|
InvenTree.models.InvenTreeTree,
|
||||||
):
|
):
|
||||||
@@ -461,6 +460,7 @@ class Part(
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
NODE_PARENT_KEY = 'variant_of'
|
NODE_PARENT_KEY = 'variant_of'
|
||||||
|
IMAGE_RENAME = rename_part_image
|
||||||
|
|
||||||
objects = PartManager()
|
objects = PartManager()
|
||||||
|
|
||||||
@@ -945,18 +945,6 @@ class Part(
|
|||||||
"""Return the web URL for viewing this part."""
|
"""Return the web URL for viewing this part."""
|
||||||
return helpers.pui_url(f'/part/{self.id}')
|
return helpers.pui_url(f'/part/{self.id}')
|
||||||
|
|
||||||
def get_image_url(self):
|
|
||||||
"""Return the URL of the image for this part."""
|
|
||||||
if self.image:
|
|
||||||
return helpers.getMediaUrl(self.image.url)
|
|
||||||
return helpers.getBlankImage()
|
|
||||||
|
|
||||||
def get_thumbnail_url(self) -> str:
|
|
||||||
"""Return the URL of the image thumbnail for this part."""
|
|
||||||
if self.image:
|
|
||||||
return helpers.getMediaUrl(self.image.thumbnail.url)
|
|
||||||
return helpers.getBlankThumbnail()
|
|
||||||
|
|
||||||
def validate_unique(self, exclude=None):
|
def validate_unique(self, exclude=None):
|
||||||
"""Validate that this Part instance is 'unique'.
|
"""Validate that this Part instance is 'unique'.
|
||||||
|
|
||||||
@@ -1127,15 +1115,6 @@ class Part(
|
|||||||
max_length=2000,
|
max_length=2000,
|
||||||
)
|
)
|
||||||
|
|
||||||
image = StdImageField(
|
|
||||||
upload_to=rename_part_image,
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
variations={'thumbnail': (128, 128), 'preview': (256, 256)},
|
|
||||||
delete_orphans=False,
|
|
||||||
verbose_name=_('Image'),
|
|
||||||
)
|
|
||||||
|
|
||||||
default_location = TreeForeignKey(
|
default_location = TreeForeignKey(
|
||||||
'stock.StockLocation',
|
'stock.StockLocation',
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ def check_stale_stock():
|
|||||||
logger.error(
|
logger.error(
|
||||||
'Error scheduling stale stock notification for user %s: %s',
|
'Error scheduling stale stock notification for user %s: %s',
|
||||||
user.username,
|
user.username,
|
||||||
str(e),
|
e,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ from djmoney.contrib.exchange.models import convert_money
|
|||||||
from djmoney.money import Money
|
from djmoney.money import Money
|
||||||
|
|
||||||
import common.currency
|
import common.currency
|
||||||
import common.models
|
|
||||||
import common.settings
|
|
||||||
import company.models
|
import company.models
|
||||||
import order.models
|
import order.models
|
||||||
import part.models
|
import part.models
|
||||||
@@ -463,11 +461,11 @@ class PartPricingTests(InvenTreeTestCase):
|
|||||||
p.delete()
|
p.delete()
|
||||||
|
|
||||||
# Check that the PartPricing object has been deleted
|
# Check that the PartPricing object has been deleted
|
||||||
self.assertFalse(part.models.PartPricing.objects.filter(part=p).exists())
|
self.assertFalse(part.models.PartPricing.objects.filter(part_id=p.pk).exists())
|
||||||
|
|
||||||
# Try to update pricing (should fail gracefully as the Part has been deleted)
|
# Try to update pricing (should fail gracefully as the Part has been deleted)
|
||||||
p.schedule_pricing_update(create=False)
|
p.schedule_pricing_update(create=False)
|
||||||
self.assertFalse(part.models.PartPricing.objects.filter(part=p).exists())
|
self.assertFalse(part.models.PartPricing.objects.filter(part_id=p.pk).exists())
|
||||||
|
|
||||||
@override_settings(TESTING_PRICING=True)
|
@override_settings(TESTING_PRICING=True)
|
||||||
def test_multi_level_bom(self):
|
def test_multi_level_bom(self):
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@@ -115,7 +113,7 @@ class SupplierBarcodeMixin(BarcodeMixin):
|
|||||||
|
|
||||||
return fields.get(key, backup_value)
|
return fields.get(key, backup_value)
|
||||||
|
|
||||||
def get_part(self) -> Optional[Part]:
|
def get_part(self) -> Part | None:
|
||||||
"""Extract the Part object from the barcode fields."""
|
"""Extract the Part object from the barcode fields."""
|
||||||
# TODO: Implement this
|
# TODO: Implement this
|
||||||
return None
|
return None
|
||||||
@@ -130,7 +128,7 @@ class SupplierBarcodeMixin(BarcodeMixin):
|
|||||||
"""Return the supplier part number from the barcode fields."""
|
"""Return the supplier part number from the barcode fields."""
|
||||||
return self.get_field_value(self.SUPPLIER_PART_NUMBER)
|
return self.get_field_value(self.SUPPLIER_PART_NUMBER)
|
||||||
|
|
||||||
def get_supplier_part(self) -> Optional[SupplierPart]:
|
def get_supplier_part(self) -> SupplierPart | None:
|
||||||
"""Return the SupplierPart object for the scanned barcode.
|
"""Return the SupplierPart object for the scanned barcode.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -174,7 +172,7 @@ class SupplierBarcodeMixin(BarcodeMixin):
|
|||||||
"""Return the manufacturer part number from the barcode fields."""
|
"""Return the manufacturer part number from the barcode fields."""
|
||||||
return self.get_field_value(self.MANUFACTURER_PART_NUMBER)
|
return self.get_field_value(self.MANUFACTURER_PART_NUMBER)
|
||||||
|
|
||||||
def get_manufacturer_part(self) -> Optional[ManufacturerPart]:
|
def get_manufacturer_part(self) -> ManufacturerPart | None:
|
||||||
"""Return the ManufacturerPart object for the scanned barcode.
|
"""Return the ManufacturerPart object for the scanned barcode.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -215,7 +213,7 @@ class SupplierBarcodeMixin(BarcodeMixin):
|
|||||||
"""Return the supplier order number from the barcode fields."""
|
"""Return the supplier order number from the barcode fields."""
|
||||||
return self.get_field_value(self.SUPPLIER_ORDER_NUMBER)
|
return self.get_field_value(self.SUPPLIER_ORDER_NUMBER)
|
||||||
|
|
||||||
def get_purchase_order(self) -> Optional[PurchaseOrder]:
|
def get_purchase_order(self) -> PurchaseOrder | None:
|
||||||
"""Extract the PurchaseOrder object from the barcode fields.
|
"""Extract the PurchaseOrder object from the barcode fields.
|
||||||
|
|
||||||
Inspect the customer_order_number and supplier_order_number fields,
|
Inspect the customer_order_number and supplier_order_number fields,
|
||||||
@@ -262,7 +260,7 @@ class SupplierBarcodeMixin(BarcodeMixin):
|
|||||||
'extract_barcode_fields must be implemented by each plugin'
|
'extract_barcode_fields must be implemented by each plugin'
|
||||||
)
|
)
|
||||||
|
|
||||||
def scan(self, barcode_data: str) -> Optional[dict]:
|
def scan(self, barcode_data: str) -> dict | None:
|
||||||
"""Perform a generic 'scan' operation on a supplier barcode.
|
"""Perform a generic 'scan' operation on a supplier barcode.
|
||||||
|
|
||||||
The supplier barcode may provide sufficient information to match against
|
The supplier barcode may provide sufficient information to match against
|
||||||
@@ -321,7 +319,7 @@ class SupplierBarcodeMixin(BarcodeMixin):
|
|||||||
location=None,
|
location=None,
|
||||||
auto_allocate: bool = True,
|
auto_allocate: bool = True,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> Optional[dict]:
|
) -> dict | None:
|
||||||
"""Attempt to receive an item against a PurchaseOrder via barcode scanning.
|
"""Attempt to receive an item against a PurchaseOrder via barcode scanning.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -432,7 +430,7 @@ class SupplierBarcodeMixin(BarcodeMixin):
|
|||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def get_supplier(self, cache: bool = False) -> Optional[Company]:
|
def get_supplier(self, cache: bool = False) -> Company | None:
|
||||||
"""Get the supplier for the SUPPLIER_ID set in the plugin settings.
|
"""Get the supplier for the SUPPLIER_ID set in the plugin settings.
|
||||||
|
|
||||||
If it's not defined, try to guess it and set it if possible.
|
If it's not defined, try to guess it and set it if possible.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Plugin class for custom data exporting."""
|
"""Plugin class for custom data exporting."""
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from typing import Optional, Union
|
from typing import Optional
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
@@ -109,9 +109,7 @@ class DataExportMixin:
|
|||||||
# The default implementation simply serializes the queryset
|
# The default implementation simply serializes the queryset
|
||||||
return serializer_class(queryset, many=True, exporting=True).data
|
return serializer_class(queryset, many=True, exporting=True).data
|
||||||
|
|
||||||
def get_export_options_serializer(
|
def get_export_options_serializer(self, **kwargs) -> serializers.Serializer | None:
|
||||||
self, **kwargs
|
|
||||||
) -> Union[serializers.Serializer, None]:
|
|
||||||
"""Return a serializer class with dynamic export options for this plugin.
|
"""Return a serializer class with dynamic export options for this plugin.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
"""Plugin mixin classes for label plugins."""
|
"""Plugin mixin classes for label plugins."""
|
||||||
|
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
@@ -223,7 +221,7 @@ class LabelPrintingMixin:
|
|||||||
|
|
||||||
def get_printing_options_serializer(
|
def get_printing_options_serializer(
|
||||||
self, request: Request, *args, **kwargs
|
self, request: Request, *args, **kwargs
|
||||||
) -> Union[serializers.Serializer, None]:
|
) -> serializers.Serializer | None:
|
||||||
"""Return a serializer class instance with dynamic printing options.
|
"""Return a serializer class instance with dynamic printing options.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Functions for processing emails."""
|
"""Functions for processing emails."""
|
||||||
|
|
||||||
from typing import Optional, Union
|
from typing import Optional
|
||||||
|
|
||||||
from django.core.mail.message import EmailMessage, EmailMultiAlternatives
|
from django.core.mail.message import EmailMessage, EmailMultiAlternatives
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ from plugin.registry import registry
|
|||||||
|
|
||||||
|
|
||||||
def process_mail_out(
|
def process_mail_out(
|
||||||
email_messages: list[Union[EmailMessage, EmailMultiAlternatives]],
|
email_messages: list[EmailMessage | EmailMultiAlternatives],
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Process email messages with plugins.
|
"""Process email messages with plugins.
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ class AutoIssueOrdersPlugin(ScheduleMixin, SettingsMixin, InvenTreePlugin):
|
|||||||
try:
|
try:
|
||||||
getattr(order, func_name)()
|
getattr(order, func_name)()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Failed to issue order %s: %s', order.pk, str(e))
|
logger.error('Failed to issue order %s: %s', order.pk, e)
|
||||||
|
|
||||||
def auto_issue_build_orders(self):
|
def auto_issue_build_orders(self):
|
||||||
"""Automatically issue build orders on the assigned target date."""
|
"""Automatically issue build orders on the assigned target date."""
|
||||||
|
|||||||
@@ -4,12 +4,8 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
import structlog
|
import structlog
|
||||||
|
|
||||||
import common.models
|
|
||||||
import common.notifications
|
import common.notifications
|
||||||
import InvenTree.helpers
|
|
||||||
import InvenTree.helpers_email
|
|
||||||
import InvenTree.helpers_model
|
import InvenTree.helpers_model
|
||||||
import InvenTree.tasks
|
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
from plugin import InvenTreePlugin
|
from plugin import InvenTreePlugin
|
||||||
from plugin.mixins import EventMixin, SettingsMixin
|
from plugin.mixins import EventMixin, SettingsMixin
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ class InvenTreeLabelSheetPlugin(LabelPrintingMixin, SettingsMixin, InvenTreePlug
|
|||||||
)
|
)
|
||||||
html += cell
|
html += cell
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception('Error rendering label: %s', str(exc))
|
logger.exception('Error rendering label: %s', exc)
|
||||||
html += """
|
html += """
|
||||||
<div class='label-sheet-cell-error'></div>
|
<div class='label-sheet-cell-error'></div>
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ def get_install_info(packagename: str) -> dict:
|
|||||||
|
|
||||||
output = error.output.decode('utf-8')
|
output = error.output.decode('utf-8')
|
||||||
info['error'] = output
|
info['error'] = output
|
||||||
logger.exception('Plugin lookup failed: %s', str(output))
|
logger.exception('Plugin lookup failed: %s', output)
|
||||||
except Exception:
|
except Exception:
|
||||||
log_error('get_install_info', scope='pip')
|
log_error('get_install_info', scope='pip')
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ def install_plugins_file():
|
|||||||
pf = settings.PLUGIN_FILE
|
pf = settings.PLUGIN_FILE
|
||||||
|
|
||||||
if not pf or not pf.exists():
|
if not pf or not pf.exists():
|
||||||
logger.warning('Plugin file %s does not exist', str(pf))
|
logger.warning('Plugin file %s does not exist', pf)
|
||||||
return
|
return
|
||||||
|
|
||||||
cmd = ['install', '--disable-pip-version-check', '-U', '-r', str(pf)]
|
cmd = ['install', '--disable-pip-version-check', '-U', '-r', str(pf)]
|
||||||
@@ -140,7 +140,7 @@ def install_plugins_file():
|
|||||||
pip_command(*cmd)
|
pip_command(*cmd)
|
||||||
except subprocess.CalledProcessError as error:
|
except subprocess.CalledProcessError as error:
|
||||||
output = error.output.decode('utf-8')
|
output = error.output.decode('utf-8')
|
||||||
logger.exception('Plugin file installation failed: %s', str(output))
|
logger.exception('Plugin file installation failed: %s', output)
|
||||||
log_error('install_plugins_file', scope='pip')
|
log_error('install_plugins_file', scope='pip')
|
||||||
return False
|
return False
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -174,7 +174,7 @@ def update_plugins_file(install_name, full_package=None, version=None, remove=Fa
|
|||||||
pf = settings.PLUGIN_FILE
|
pf = settings.PLUGIN_FILE
|
||||||
|
|
||||||
if not pf or not pf.exists():
|
if not pf or not pf.exists():
|
||||||
logger.warning('Plugin file %s does not exist', str(pf))
|
logger.warning('Plugin file %s does not exist', pf)
|
||||||
return
|
return
|
||||||
|
|
||||||
def compare_line(line: str):
|
def compare_line(line: str):
|
||||||
@@ -186,7 +186,7 @@ def update_plugins_file(install_name, full_package=None, version=None, remove=Fa
|
|||||||
with pf.open(mode='r') as f:
|
with pf.open(mode='r') as f:
|
||||||
lines = f.readlines()
|
lines = f.readlines()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception('Failed to read plugins file: %s', str(exc))
|
logger.exception('Failed to read plugins file: %s', exc)
|
||||||
log_error('update_plugins_file', scope='plugins')
|
log_error('update_plugins_file', scope='plugins')
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -223,7 +223,7 @@ def update_plugins_file(install_name, full_package=None, version=None, remove=Fa
|
|||||||
if not line.endswith('\n'):
|
if not line.endswith('\n'):
|
||||||
f.write('\n')
|
f.write('\n')
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception('Failed to add plugin to plugins file: %s', str(exc))
|
logger.exception('Failed to add plugin to plugins file: %s', exc)
|
||||||
log_error('update_plugins_file', scope='plugins')
|
log_error('update_plugins_file', scope='plugins')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from datetime import datetime
|
|||||||
from distutils.sysconfig import get_python_lib # type: ignore[import]
|
from distutils.sysconfig import get_python_lib # type: ignore[import]
|
||||||
from importlib.metadata import PackageNotFoundError, metadata
|
from importlib.metadata import PackageNotFoundError, metadata
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Union
|
from typing import Optional
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
@@ -483,7 +483,7 @@ class InvenTreePlugin(VersionMixin, MixinBase, MetaBase):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@mark_final
|
@mark_final
|
||||||
def check_package_install_name(cls) -> Union[str, None]:
|
def check_package_install_name(cls) -> str | None:
|
||||||
"""Installable package name of the plugin.
|
"""Installable package name of the plugin.
|
||||||
|
|
||||||
e.g. if this plugin was installed via 'pip install <x>',
|
e.g. if this plugin was installed via 'pip install <x>',
|
||||||
@@ -496,7 +496,7 @@ class InvenTreePlugin(VersionMixin, MixinBase, MetaBase):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
@mark_final
|
@mark_final
|
||||||
def package_install_name(self) -> Union[str, None]:
|
def package_install_name(self) -> str | None:
|
||||||
"""Installable package name of the plugin.
|
"""Installable package name of the plugin.
|
||||||
|
|
||||||
e.g. if this plugin was installed via 'pip install <x>',
|
e.g. if this plugin was installed via 'pip install <x>',
|
||||||
@@ -610,7 +610,7 @@ class InvenTreePlugin(VersionMixin, MixinBase, MetaBase):
|
|||||||
|
|
||||||
return url
|
return url
|
||||||
|
|
||||||
def get_admin_source(self) -> Union[str, None]:
|
def get_admin_source(self) -> str | None:
|
||||||
"""Return a path to a JavaScript file which contains custom UI settings.
|
"""Return a path to a JavaScript file which contains custom UI settings.
|
||||||
|
|
||||||
The frontend code expects that this file provides a function named 'renderPluginSettings'.
|
The frontend code expects that this file provides a function named 'renderPluginSettings'.
|
||||||
@@ -620,7 +620,7 @@ class InvenTreePlugin(VersionMixin, MixinBase, MetaBase):
|
|||||||
|
|
||||||
return self.plugin_static_file(self.ADMIN_SOURCE)
|
return self.plugin_static_file(self.ADMIN_SOURCE)
|
||||||
|
|
||||||
def get_admin_context(self) -> Union[dict, None]:
|
def get_admin_context(self) -> dict | None:
|
||||||
"""Return a context dictionary for the admin panel settings.
|
"""Return a context dictionary for the admin panel settings.
|
||||||
|
|
||||||
This is an optional method which can be overridden by the plugin.
|
This is an optional method which can be overridden by the plugin.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from collections import OrderedDict
|
|||||||
from importlib.machinery import SourceFileLoader
|
from importlib.machinery import SourceFileLoader
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from typing import Any, Optional, Union
|
from typing import Any, Optional
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@@ -212,7 +212,7 @@ class PluginsRegistry:
|
|||||||
|
|
||||||
return plg
|
return plg
|
||||||
|
|
||||||
def get_plugin_config(self, slug: str, name: Union[str, None] = None):
|
def get_plugin_config(self, slug: str, name: str | None = None):
|
||||||
"""Return the matching PluginConfig instance for a given plugin.
|
"""Return the matching PluginConfig instance for a given plugin.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -820,7 +820,7 @@ class PluginsRegistry:
|
|||||||
logger.exception(
|
logger.exception(
|
||||||
'[PLUGIN] Encountered an error with %s:\n%s',
|
'[PLUGIN] Encountered an error with %s:\n%s',
|
||||||
getattr(error, 'path', None),
|
getattr(error, 'path', None),
|
||||||
str(error),
|
error,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug('Finished plugin initialization')
|
logger.debug('Finished plugin initialization')
|
||||||
@@ -980,9 +980,8 @@ class PluginsRegistry:
|
|||||||
|
|
||||||
if old_hash != self.registry_hash:
|
if old_hash != self.registry_hash:
|
||||||
try:
|
try:
|
||||||
logger.info(
|
logger.info('Updating plugin registry hash: %s', self.registry_hash)
|
||||||
'Updating plugin registry hash: %s', str(self.registry_hash)
|
|
||||||
)
|
|
||||||
set_global_setting(
|
set_global_setting(
|
||||||
'_PLUGIN_REGISTRY_HASH', self.registry_hash, change_user=None
|
'_PLUGIN_REGISTRY_HASH', self.registry_hash, change_user=None
|
||||||
)
|
)
|
||||||
@@ -991,7 +990,7 @@ class PluginsRegistry:
|
|||||||
pass
|
pass
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
# Some other exception, we want to know about it
|
# Some other exception, we want to know about it
|
||||||
logger.exception('Failed to update plugin registry hash: %s', str(exc))
|
logger.exception('Failed to update plugin registry hash: %s', exc)
|
||||||
|
|
||||||
def plugin_settings_keys(self):
|
def plugin_settings_keys(self):
|
||||||
"""A list of keys which are used to store plugin settings."""
|
"""A list of keys which are used to store plugin settings."""
|
||||||
@@ -1064,7 +1063,7 @@ class PluginsRegistry:
|
|||||||
try:
|
try:
|
||||||
reg_hash = get_global_setting('_PLUGIN_REGISTRY_HASH', '', create=False)
|
reg_hash = get_global_setting('_PLUGIN_REGISTRY_HASH', '', create=False)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception('Failed to retrieve plugin registry hash: %s', str(exc))
|
logger.exception('Failed to retrieve plugin registry hash: %s', exc)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if reg_hash and reg_hash != self.registry_hash:
|
if reg_hash and reg_hash != self.registry_hash:
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ def copy_plugin_static_files(slug, check_reload=True):
|
|||||||
with item.open('rb') as src:
|
with item.open('rb') as src:
|
||||||
staticfiles_storage.save(destination_path, src)
|
staticfiles_storage.save(destination_path, src)
|
||||||
|
|
||||||
logger.debug('- copied %s to %s', str(item), str(destination_path))
|
logger.debug('- copied %s to %s', item, destination_path)
|
||||||
copied += 1
|
copied += 1
|
||||||
|
|
||||||
if copied > 0:
|
if copied > 0:
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ def report_page_size_default():
|
|||||||
try:
|
try:
|
||||||
page_size = get_global_setting('REPORT_DEFAULT_PAGE_SIZE', 'A4', create=False)
|
page_size = get_global_setting('REPORT_DEFAULT_PAGE_SIZE', 'A4', create=False)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception('Error getting default page size: %s', str(exc))
|
logger.exception('Error getting default page size: %s', exc)
|
||||||
page_size = 'A4'
|
page_size = 'A4'
|
||||||
|
|
||||||
return page_size
|
return page_size
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
import plugin.models
|
|
||||||
import plugin.serializers
|
import plugin.serializers
|
||||||
import report.helpers
|
import report.helpers
|
||||||
import report.models
|
import report.models
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
from decimal import Decimal, InvalidOperation
|
from decimal import Decimal, InvalidOperation
|
||||||
from typing import Any, Optional, Union
|
from typing import Any, Optional
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.apps.registry import apps
|
from django.apps.registry import apps
|
||||||
@@ -323,18 +323,9 @@ def part_image(part: Part, preview: bool = False, thumbnail: bool = False, **kwa
|
|||||||
"""
|
"""
|
||||||
if type(part) is not Part:
|
if type(part) is not Part:
|
||||||
raise TypeError(_('part_image tag requires a Part instance'))
|
raise TypeError(_('part_image tag requires a Part instance'))
|
||||||
|
return uploaded_image(
|
||||||
part_img = part.image
|
InvenTree.helpers.image2name(part.image, preview, thumbnail), **kwargs
|
||||||
if not part_img:
|
)
|
||||||
img = None
|
|
||||||
elif preview:
|
|
||||||
img = None if not hasattr(part.image, 'preview') else part_img.preview.name
|
|
||||||
elif thumbnail:
|
|
||||||
img = None if not hasattr(part.image, 'thumbnail') else part_img.thumbnail.name
|
|
||||||
else:
|
|
||||||
img = part.image.name
|
|
||||||
|
|
||||||
return uploaded_image(img, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
@@ -369,18 +360,9 @@ def company_image(
|
|||||||
"""
|
"""
|
||||||
if type(company) is not Company:
|
if type(company) is not Company:
|
||||||
raise TypeError(_('company_image tag requires a Company instance'))
|
raise TypeError(_('company_image tag requires a Company instance'))
|
||||||
|
return uploaded_image(
|
||||||
cmp_img = company.image
|
InvenTree.helpers.image2name(company.image, preview, thumbnail), **kwargs
|
||||||
if not cmp_img:
|
)
|
||||||
img = None
|
|
||||||
elif preview:
|
|
||||||
img = cmp_img.preview.name
|
|
||||||
elif thumbnail:
|
|
||||||
img = cmp_img.thumbnail.name
|
|
||||||
else:
|
|
||||||
img = cmp_img.name
|
|
||||||
|
|
||||||
return uploaded_image(img, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
@@ -603,7 +585,7 @@ def render_currency(money, **kwargs):
|
|||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def create_currency(
|
def create_currency(
|
||||||
amount: Union[str, int, float, Decimal], currency: Optional[str] = None, **kwargs
|
amount: str | int | float | Decimal, currency: Optional[str] = None, **kwargs
|
||||||
):
|
):
|
||||||
"""Create a Money object, with the provided amount and currency.
|
"""Create a Money object, with the provided amount and currency.
|
||||||
|
|
||||||
@@ -694,9 +676,9 @@ def render_html_text(text: str, **kwargs):
|
|||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def format_number(
|
def format_number(
|
||||||
number: Union[int, float, Decimal],
|
number: int | float | Decimal,
|
||||||
decimal_places: Optional[int] = None,
|
decimal_places: Optional[int] = None,
|
||||||
multiplier: Optional[Union[int, float, Decimal]] = None,
|
multiplier: Optional[int | float | Decimal] = None,
|
||||||
integer: bool = False,
|
integer: bool = False,
|
||||||
leading: int = 0,
|
leading: int = 0,
|
||||||
separator: Optional[str] = None,
|
separator: Optional[str] = None,
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ class ReportTagTest(PartImageTestMixin, InvenTreeTestCase):
|
|||||||
|
|
||||||
obj = Part.objects.create(name='test', description='test')
|
obj = Part.objects.create(name='test', description='test')
|
||||||
self.create_test_image()
|
self.create_test_image()
|
||||||
|
obj.refresh_from_db()
|
||||||
|
|
||||||
report_tags.part_image(obj, preview=True)
|
report_tags.part_image(obj, preview=True)
|
||||||
report_tags.part_image(obj, thumbnail=True)
|
report_tags.part_image(obj, thumbnail=True)
|
||||||
|
|||||||
@@ -1557,9 +1557,7 @@ class StockTrackingList(
|
|||||||
deltas = item['deltas'] or {}
|
deltas = item['deltas'] or {}
|
||||||
|
|
||||||
if key in deltas:
|
if key in deltas:
|
||||||
item['deltas'][f'{key}_detail'] = related_data.get(
|
item['deltas'][f'{key}_detail'] = related_data.get(deltas[key])
|
||||||
deltas[key], None
|
|
||||||
)
|
|
||||||
|
|
||||||
if page is not None:
|
if page is not None:
|
||||||
return self.get_paginated_response(data)
|
return self.get_paginated_response(data)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from __future__ import annotations
|
|||||||
import os
|
import os
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from decimal import Decimal, InvalidOperation
|
from decimal import Decimal, InvalidOperation
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
@@ -129,7 +128,7 @@ class StockLocationReportContext(report.mixins.BaseReportContext):
|
|||||||
|
|
||||||
location: StockLocation
|
location: StockLocation
|
||||||
qr_data: str
|
qr_data: str
|
||||||
parent: Optional[StockLocation]
|
parent: StockLocation | None
|
||||||
stock_location: StockLocation
|
stock_location: StockLocation
|
||||||
stock_items: report.mixins.QuerySet[StockItem]
|
stock_items: report.mixins.QuerySet[StockItem]
|
||||||
|
|
||||||
@@ -392,7 +391,7 @@ class StockItemReportContext(report.mixins.BaseReportContext):
|
|||||||
barcode_hash: str
|
barcode_hash: str
|
||||||
batch: str
|
batch: str
|
||||||
child_items: report.mixins.QuerySet[StockItem]
|
child_items: report.mixins.QuerySet[StockItem]
|
||||||
ipn: Optional[str]
|
ipn: str | None
|
||||||
installed_items: set[StockItem]
|
installed_items: set[StockItem]
|
||||||
item: StockItem
|
item: StockItem
|
||||||
name: str
|
name: str
|
||||||
@@ -403,7 +402,7 @@ class StockItemReportContext(report.mixins.BaseReportContext):
|
|||||||
quantity: Decimal
|
quantity: Decimal
|
||||||
result_list: list[StockItemTestResult]
|
result_list: list[StockItemTestResult]
|
||||||
results: dict[str, StockItemTestResult]
|
results: dict[str, StockItemTestResult]
|
||||||
serial: Optional[str]
|
serial: str | None
|
||||||
stock_item: StockItem
|
stock_item: StockItem
|
||||||
tests: dict[str, StockItemTestResult]
|
tests: dict[str, StockItemTestResult]
|
||||||
test_keys: list[str]
|
test_keys: list[str]
|
||||||
@@ -667,7 +666,7 @@ class StockItem(
|
|||||||
return items
|
return items
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def convert_serial_to_int(serial: str) -> Optional[int]:
|
def convert_serial_to_int(serial: str) -> int | None:
|
||||||
"""Convert the provided serial number to an integer value.
|
"""Convert the provided serial number to an integer value.
|
||||||
|
|
||||||
This function hooks into the plugin system to allow for custom serial number conversion.
|
This function hooks into the plugin system to allow for custom serial number conversion.
|
||||||
@@ -1783,7 +1782,7 @@ class StockItem(
|
|||||||
self,
|
self,
|
||||||
entry_type: int,
|
entry_type: int,
|
||||||
user: User,
|
user: User,
|
||||||
deltas: Optional[dict] = None,
|
deltas: dict | None = None,
|
||||||
notes: str = '',
|
notes: str = '',
|
||||||
commit: bool = True,
|
commit: bool = True,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@@ -1842,9 +1841,9 @@ class StockItem(
|
|||||||
self,
|
self,
|
||||||
quantity: int,
|
quantity: int,
|
||||||
serials: list[str],
|
serials: list[str],
|
||||||
user: Optional[User] = None,
|
user: User | None = None,
|
||||||
notes: Optional[str] = '',
|
notes: str | None = '',
|
||||||
location: Optional[StockLocation] = None,
|
location: StockLocation | None = None,
|
||||||
):
|
):
|
||||||
"""Split this stock item into unique serial numbers.
|
"""Split this stock item into unique serial numbers.
|
||||||
|
|
||||||
@@ -1951,7 +1950,7 @@ class StockItem(
|
|||||||
item.save()
|
item.save()
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def copyTestResultsFrom(self, other: StockItem, filters: Optional[dict] = None):
|
def copyTestResultsFrom(self, other: StockItem, filters: dict | None = None):
|
||||||
"""Copy all test results from another StockItem."""
|
"""Copy all test results from another StockItem."""
|
||||||
# Set default - see B006
|
# Set default - see B006
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
"""DRF API serializers for the 'users' app."""
|
"""DRF API serializers for the 'users' app."""
|
||||||
|
|
||||||
|
import secrets
|
||||||
|
import string
|
||||||
|
|
||||||
from django.contrib.auth.models import Group, Permission, User
|
from django.contrib.auth.models import Group, Permission, User
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@@ -382,6 +385,20 @@ class MeUserSerializer(ExtendedUserSerializer):
|
|||||||
profile = UserProfileSerializer(many=False, read_only=True)
|
profile = UserProfileSerializer(many=False, read_only=True)
|
||||||
|
|
||||||
|
|
||||||
|
def make_random_password(length=14):
|
||||||
|
"""Generate a random password of given length."""
|
||||||
|
alphabet = string.ascii_letters + string.digits
|
||||||
|
while True:
|
||||||
|
password = ''.join(secrets.choice(alphabet) for i in range(length))
|
||||||
|
if (
|
||||||
|
any(c.islower() for c in password)
|
||||||
|
and any(c.isupper() for c in password)
|
||||||
|
and sum(c.isdigit() for c in password) >= 3
|
||||||
|
):
|
||||||
|
break
|
||||||
|
return password
|
||||||
|
|
||||||
|
|
||||||
class UserCreateSerializer(ExtendedUserSerializer):
|
class UserCreateSerializer(ExtendedUserSerializer):
|
||||||
"""Serializer for creating a new User."""
|
"""Serializer for creating a new User."""
|
||||||
|
|
||||||
@@ -402,7 +419,7 @@ class UserCreateSerializer(ExtendedUserSerializer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Generate a random password
|
# Generate a random password
|
||||||
password = User.objects.make_random_password(length=14)
|
password = make_random_password(length=14)
|
||||||
attrs.update({'password': password})
|
attrs.update({'password': password})
|
||||||
return super().validate(attrs)
|
return super().validate(attrs)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
import json
|
import json
|
||||||
import json.decoder
|
import json.decoder
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@@ -18,7 +17,7 @@ FRONTEND_SETTINGS = json.dumps(settings.FRONTEND_SETTINGS)
|
|||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def spa_bundle(manifest_path: Union[str, Path] = '', app: str = 'web'):
|
def spa_bundle(manifest_path: str | Path = '', app: str = 'web'):
|
||||||
"""Render SPA bundle."""
|
"""Render SPA bundle."""
|
||||||
|
|
||||||
def get_url(file: str) -> str:
|
def get_url(file: str) -> str:
|
||||||
|
|||||||
+188
-204
@@ -6,7 +6,6 @@ asgiref==3.10.0 \
|
|||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# django
|
# django
|
||||||
# django-stubs
|
|
||||||
build==1.3.0 \
|
build==1.3.0 \
|
||||||
--hash=sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397 \
|
--hash=sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397 \
|
||||||
--hash=sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4
|
--hash=sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4
|
||||||
@@ -227,154 +226,159 @@ charset-normalizer==3.4.4 \
|
|||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# pdfminer-six
|
# pdfminer-six
|
||||||
# requests
|
# requests
|
||||||
click==8.1.8 \
|
click==8.3.0 \
|
||||||
--hash=sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2 \
|
--hash=sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc \
|
||||||
--hash=sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a
|
--hash=sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4
|
||||||
# via pip-tools
|
# via pip-tools
|
||||||
coverage[toml]==7.10.7 \
|
coverage[toml]==7.11.2 \
|
||||||
--hash=sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9 \
|
--hash=sha256:004bdc5985b86f565772af627925e368256ee2172623db10a0d78a3b53f20ef1 \
|
||||||
--hash=sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880 \
|
--hash=sha256:03e7e7dc31a7deaebf121c3c3bd3c6442b7fbf50aca72aae2a1d08aa30ca2a20 \
|
||||||
--hash=sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999 \
|
--hash=sha256:07e14a4050525fd98bf3d793f229eb8b3ae81678f4031e38e6a18a068bd59fd4 \
|
||||||
--hash=sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1 \
|
--hash=sha256:0f4a958ff286038ac870f836351e9fb8912f1614d1cdbda200fc899235f7dc9b \
|
||||||
--hash=sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13 \
|
--hash=sha256:10f10c9acf584ef82bfaaa7296163bd11c7487237f1670e81fc2fa7e972be67b \
|
||||||
--hash=sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b \
|
--hash=sha256:17047fb65fcd1ce8a2f97dd2247c2b59cb4bc8848b3911db02dcb05856f91b71 \
|
||||||
--hash=sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82 \
|
--hash=sha256:1ac3f647ecf25d883051ef42d38d823016e715b9f289f8c1768be5117075d1bd \
|
||||||
--hash=sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973 \
|
--hash=sha256:230317450af65a37c1fdbdd3546f7277e0c1c1b65e0d57409248e5dd0fa13493 \
|
||||||
--hash=sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f \
|
--hash=sha256:2442afabe9e83b881be083238bb7cf5afd4a10e47f29b6094470338d2336b33c \
|
||||||
--hash=sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681 \
|
--hash=sha256:2970c03fefee2a5f1aebc91201a0706a7d0061cc71ab452bb5c5345b7174a349 \
|
||||||
--hash=sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0 \
|
--hash=sha256:2ca963994d28e44285dc104cf94b25d8a7fd0c6f87cf944f46a23f473910703f \
|
||||||
--hash=sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541 \
|
--hash=sha256:30c437e8b51ce081fe3903c9e368e85c9a803b093fd062c49215f3bf4fd1df37 \
|
||||||
--hash=sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32 \
|
--hash=sha256:36c41bf2ee6f6062de8177e249fee17cd5c9662cd373f7a41e6468a34c5b9c0f \
|
||||||
--hash=sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17 \
|
--hash=sha256:38a5509fe7fabb6fb3161059b947641753b6529150ef483fc01c4516a546f2ad \
|
||||||
--hash=sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a \
|
--hash=sha256:397778cf6d50df59c890bd3ac10acb5bf413388ff6a013305134f1403d5db648 \
|
||||||
--hash=sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40 \
|
--hash=sha256:3aa8c62460499e10ceac5ea61cc09c4f7ddcd8a68c6313cf08785ad353dfd311 \
|
||||||
--hash=sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd \
|
--hash=sha256:410cafc1aba1f7eb8c09823d5da381be30a2c9b3595758a4c176fcfc04732731 \
|
||||||
--hash=sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6 \
|
--hash=sha256:43ecf9dca4fcb3baf8a886019dd5ce663c95a5e1c5172719c414f0ebd9eeb785 \
|
||||||
--hash=sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7 \
|
--hash=sha256:44b6e04bb94e59927a2807cd4de86386ce34248eaea95d9f1049a72f81828c38 \
|
||||||
--hash=sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb \
|
--hash=sha256:461577af3f8ad4da244a55af66c0731b68540ce571dbdc02598b5ec9e7a09e73 \
|
||||||
--hash=sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f \
|
--hash=sha256:4648c90cf741fb61e142826db1557a44079de0ca868c5c5a363c53d852897e84 \
|
||||||
--hash=sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d \
|
--hash=sha256:4aa799c61869318d2b86c0d3c413d6805546aec42069f009cbb27df2eefb2790 \
|
||||||
--hash=sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe \
|
--hash=sha256:4aaf2212302b6f748dde596424b0f08bc3e1285192104e2480f43d56b6824f35 \
|
||||||
--hash=sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c \
|
--hash=sha256:4c4423ea9c28749080b41e18ec74d658e6c9f148a6b47e719f3d7f56197f8227 \
|
||||||
--hash=sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807 \
|
--hash=sha256:4d1ff4b87ad438148976f2215141a490ae000e878536370d53f8da8c59a175a6 \
|
||||||
--hash=sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab \
|
--hash=sha256:4f8f6bcaa7fe162460abb38f7a5dbfd7f47cfc51e2a0bf0d3ef9e51427298391 \
|
||||||
--hash=sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2 \
|
--hash=sha256:55ae008253df6000bc885a780c1b0e939bd8c932f41e16df1cfe19a00428a98a \
|
||||||
--hash=sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546 \
|
--hash=sha256:595c6bb2b565cc2d930ee634cae47fa959dfd24cc0e8ae4cf2b6e7e131e0d1f7 \
|
||||||
--hash=sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e \
|
--hash=sha256:5a02818ec44803e325d66bd022828212df934739b894d1699c9a05b9105d30f2 \
|
||||||
--hash=sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65 \
|
--hash=sha256:5b284931d57389ec97a63fb1edf91c68ec369cee44bc40b37b5c3985ba0a2914 \
|
||||||
--hash=sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396 \
|
--hash=sha256:5c31cdbb95ab0f4a60224a04efc43cfb406ce904f0b60fb6b2a72f37718ea5cb \
|
||||||
--hash=sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431 \
|
--hash=sha256:5db683000ff6217273071c752bd6a1d341b6dc5d6aaa56678c53577a4e70e78a \
|
||||||
--hash=sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb \
|
--hash=sha256:5f72a49504e1f35443b157d97997c9259a017384373eab52fd09b8ade2ae4674 \
|
||||||
--hash=sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699 \
|
--hash=sha256:61d6a7cc1e7a7a761ac59dcc88cee54219fd4231face52bd1257cfd3df29ae9f \
|
||||||
--hash=sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0 \
|
--hash=sha256:632904d126ca97e5d4ecf7e51ae8b20f086b6f002c6075adcfd4ff3a28574527 \
|
||||||
--hash=sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f \
|
--hash=sha256:6681164bc697b93676945c8c814b76ac72204c395e11b71ba796a93b33331c24 \
|
||||||
--hash=sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a \
|
--hash=sha256:689d3b4dd0d4c912ed8bfd7a1b5ff2c5ecb1fa16571840573174704ff5437862 \
|
||||||
--hash=sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235 \
|
--hash=sha256:6f70fa1ef17cba5dada94e144ea1b6e117d4f174666842d1da3aaf765d6eb477 \
|
||||||
--hash=sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911 \
|
--hash=sha256:72a3d109ac233666064d60b29ae5801dd28bc51d1990e69f183a2b91b92d4baf \
|
||||||
--hash=sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23 \
|
--hash=sha256:75ef769be19d69ea71b0417d7fbf090032c444792579cdf9b166346a340987d5 \
|
||||||
--hash=sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87 \
|
--hash=sha256:7e01ab8d69b6cffa2463e78a4d760a6b69dfebe5bf21837eabcc273655c7e7b3 \
|
||||||
--hash=sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699 \
|
--hash=sha256:7ea36e981a8a591acdaa920704f8dc798f9fff356c97dbd5d5702046ae967ce1 \
|
||||||
--hash=sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a \
|
--hash=sha256:7f1aa017b47e1879d7bac50161b00d2b886f2ff3882fa09427119e1b3572ede1 \
|
||||||
--hash=sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b \
|
--hash=sha256:7f933bc1fead57373922e383d803e1dd5ec7b5a786c220161152ebee1aa3f006 \
|
||||||
--hash=sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256 \
|
--hash=sha256:8014a28a37ffabf7da7107f4f154d68c6b89672f27fef835a0574591c5cd140b \
|
||||||
--hash=sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a \
|
--hash=sha256:805efa416085999da918f15f81b26636d8e79863e1fbac1495664686d1e6a6e9 \
|
||||||
--hash=sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417 \
|
--hash=sha256:811bff1f93566a8556a9aeb078bd82573e37f4d802a185fba4cbe75468615050 \
|
||||||
--hash=sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0 \
|
--hash=sha256:84e8e0f5ab5134a2d32d4ebadc18b433dbbeddd0b73481f816333b1edd3ff1c8 \
|
||||||
--hash=sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a \
|
--hash=sha256:87d7c7b0b2279e174f36d276e2afb7bf16c9ea04e824d4fa277eea1854f4cfd4 \
|
||||||
--hash=sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360 \
|
--hash=sha256:89565d7c9340858424a5ca3223bfefe449aeb116942cdc98cd76c07ca50e9db8 \
|
||||||
--hash=sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0 \
|
--hash=sha256:940d195f4c8ba3ec6e7c302c9f546cdbe63e57289ed535452bc52089b1634f1c \
|
||||||
--hash=sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b \
|
--hash=sha256:94ced4a29a6987af99faaa49a513bf8d0458e8af004c54174e05dd7a8a31c7d9 \
|
||||||
--hash=sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb \
|
--hash=sha256:9a6468e1a3a40d3d1f9120a9ff221d3eacef4540a6f819fff58868fe0bd44fa9 \
|
||||||
--hash=sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2 \
|
--hash=sha256:9a95b7a6043b221ec1a0d4d5481e424272b37028353265fbe5fcd3768d652eb7 \
|
||||||
--hash=sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d \
|
--hash=sha256:9f5f6ee021b3b25e748a9a053f3a8dd61a62b6689efd6425cb47e27360994903 \
|
||||||
--hash=sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a \
|
--hash=sha256:a35701fe0b5ee9d4b67d31aa76555237af32a36b0cf8dd33f8a74470cf7cd2f5 \
|
||||||
--hash=sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e \
|
--hash=sha256:a913b21f716aa05b149a8656e9e234d9da04bc1f9842136ad25a53172fecc20e \
|
||||||
--hash=sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69 \
|
--hash=sha256:ae43149b7732df15c3ca9879b310c48b71d08cd8a7ba77fda7f9108f78499e93 \
|
||||||
--hash=sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14 \
|
--hash=sha256:b4776c6555a9f378f37fa06408f2e1cc1d06e4c4e06adb3d157a4926b549efbe \
|
||||||
--hash=sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d \
|
--hash=sha256:b7658f3d4f728092368c091c18efcfb679be9b612c93bfdf345f33635a325188 \
|
||||||
--hash=sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f \
|
--hash=sha256:b7fc943097fa48de00d14d2a2f3bcebfede024e031d7cd96063fe135f8cbe96e \
|
||||||
--hash=sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2 \
|
--hash=sha256:b9f28b900d96d83e2ae855b68d5cf5a704fa0b5e618999133fd2fb3bbe35ecb1 \
|
||||||
--hash=sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c \
|
--hash=sha256:bc65e32fe5bb942f0f5247e1500e355cbbdf326181198f5e27e3bb3ddb81e203 \
|
||||||
--hash=sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0 \
|
--hash=sha256:bee1911c44c52cad6b51d436aa8c6ff5ca5d414fa089c7444592df9e7b890be9 \
|
||||||
--hash=sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399 \
|
--hash=sha256:c4b1bea4c707f4c09f682fe0e646a114dfd068f627880d4a208850d01f8164ad \
|
||||||
--hash=sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59 \
|
--hash=sha256:c5769159986eb174f0f66d049a52da03f2d976ac1355679371f1269e83528599 \
|
||||||
--hash=sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63 \
|
--hash=sha256:c65f4291aec39692a3bfbe1d92ae5bea58c16b5553fdf021de61c655d987233f \
|
||||||
--hash=sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b \
|
--hash=sha256:c7ea5dec77d79dabb7b5fc712c59361aac52e459cd22028480625c3c743323d0 \
|
||||||
--hash=sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2 \
|
--hash=sha256:c85f44ed4260221e46a4e9e8e8df4b359ab6c0a742c79e85d649779bcf77b534 \
|
||||||
--hash=sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e \
|
--hash=sha256:c8b9a7ebc6a29202fb095877fd8362aab09882894d1c950060c76d61fb116114 \
|
||||||
--hash=sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0 \
|
--hash=sha256:cbffd1d5c5bf4c576ca247bf77646cdad4dced82928337eeb0b85e2b3be4d64b \
|
||||||
--hash=sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520 \
|
--hash=sha256:d0e80c9946da61cc0bf55dfd90d65707acc1aa5bdcb551d4285ea8906255bb33 \
|
||||||
--hash=sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df \
|
--hash=sha256:d30a717493583c2a83c99f195e934c073be7f4291b32b7352c246d52e43f6893 \
|
||||||
--hash=sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c \
|
--hash=sha256:d423991415f73a70c0a5f3e0a226cf4ab374dd0da7409978069b844df3d31582 \
|
||||||
--hash=sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b \
|
--hash=sha256:d73da4893125e0671f762e408dea9957b2bda0036c9589c2fd258a6b870acbdb \
|
||||||
--hash=sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2 \
|
--hash=sha256:d752a8e398a19e2fb24781e4c73089bfeb417b6ac55f96c2c42cfe5bdb21cc18 \
|
||||||
--hash=sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f \
|
--hash=sha256:e3b92e10ca996b5421232dd6629b9933f97eb57ce374bca800ab56681fbeda2b \
|
||||||
--hash=sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61 \
|
--hash=sha256:e448ceee2fb880427eafc9a3f8e6162b2ac7cc3e9b30b85d6511f25cc8a11820 \
|
||||||
--hash=sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a \
|
--hash=sha256:e48b95abe2983be98cdf52900e07127eb7fe7067c87a700851f4f1f53d2b00e6 \
|
||||||
--hash=sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59 \
|
--hash=sha256:e52a028a56889d3ad036c0420e866e4a69417d3203e2fc5f03dcb8841274b64c \
|
||||||
--hash=sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c \
|
--hash=sha256:e7d3fccd5781c5d29ca0bd1ea272630f05cd40a71d419e7e6105c0991400eb14 \
|
||||||
--hash=sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf \
|
--hash=sha256:e8eb6cbd7d3b238335b5da0f3ce281102435afb503be4d7bdd69eea3c700a952 \
|
||||||
--hash=sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07 \
|
--hash=sha256:ea10a57568af7cf082a7a4d98a699f993652c2ffbdd5a6c9d63c9ca10b693b4d \
|
||||||
--hash=sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6 \
|
--hash=sha256:ea910cc737ee8553c81ad5c104bc5b135106ebb36f88be506c3493e001b4c733 \
|
||||||
--hash=sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e \
|
--hash=sha256:eaa2a5eeb82fa7a6a9cd65c4f968ee2a53839d451b4e88e060c67d87a0a40732 \
|
||||||
--hash=sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594 \
|
--hash=sha256:ed6ba9f1777fdd1c8e5650c7d123211fa484a187c61af4d82948dc5ee3c0afcc \
|
||||||
--hash=sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49 \
|
--hash=sha256:ef2d3081562cd83f97984a96e02e7a294efa28f58d5e7f4e28920f59fd752b41 \
|
||||||
--hash=sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843 \
|
--hash=sha256:f633da28958f57b846e955d28661b2b323d8ae84668756e1eea64045414dbe34 \
|
||||||
--hash=sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14 \
|
--hash=sha256:f6b2498f86f2554ed6cb8df64201ee95b8c70fb77064a8b2ae8a7185e7a4a5f0 \
|
||||||
--hash=sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3 \
|
--hash=sha256:f6f985e175dfa1fb8c0a01f47186720ae25d5e20c181cc5f3b9eba95589b8148 \
|
||||||
--hash=sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1 \
|
--hash=sha256:f80cb5b328e870bf3df0568b41643a85ee4b8ccd219a096812389e39aa310ea4 \
|
||||||
--hash=sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698 \
|
--hash=sha256:fd3f7cc6cb999e3eff91a2998a70c662b0fcd3c123d875766147c530ca0d3248
|
||||||
--hash=sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15 \
|
|
||||||
--hash=sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d \
|
|
||||||
--hash=sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5 \
|
|
||||||
--hash=sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e \
|
|
||||||
--hash=sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0 \
|
|
||||||
--hash=sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b \
|
|
||||||
--hash=sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239 \
|
|
||||||
--hash=sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba \
|
|
||||||
--hash=sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4 \
|
|
||||||
--hash=sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260 \
|
|
||||||
--hash=sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a \
|
|
||||||
--hash=sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3
|
|
||||||
# via -r src/backend/requirements-dev.in
|
# via -r src/backend/requirements-dev.in
|
||||||
cryptography==44.0.3 \
|
cryptography==46.0.3 \
|
||||||
--hash=sha256:02f55fb4f8b79c1221b0961488eaae21015b69b210e18c386b69de182ebb1259 \
|
--hash=sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217 \
|
||||||
--hash=sha256:157f1f3b8d941c2bd8f3ffee0af9b049c9665c39d3da9db2dc338feca5e98a43 \
|
--hash=sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d \
|
||||||
--hash=sha256:192ed30fac1728f7587c6f4613c29c584abdc565d7417c13904708db10206645 \
|
--hash=sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc \
|
||||||
--hash=sha256:21a83f6f35b9cc656d71b5de8d519f566df01e660ac2578805ab245ffd8523f8 \
|
--hash=sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71 \
|
||||||
--hash=sha256:25cd194c39fa5a0aa4169125ee27d1172097857b27109a45fadc59653ec06f44 \
|
--hash=sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971 \
|
||||||
--hash=sha256:3883076d5c4cc56dbef0b898a74eb6992fdac29a7b9013870b34efe4ddb39a0d \
|
--hash=sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a \
|
||||||
--hash=sha256:3bb0847e6363c037df8f6ede57d88eaf3410ca2267fb12275370a76f85786a6f \
|
--hash=sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926 \
|
||||||
--hash=sha256:3be3f649d91cb182c3a6bd336de8b61a0a71965bd13d1a04a0e15b39c3d5809d \
|
--hash=sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc \
|
||||||
--hash=sha256:3f07943aa4d7dad689e3bb1638ddc4944cc5e0921e3c227486daae0e31a05e54 \
|
--hash=sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d \
|
||||||
--hash=sha256:479d92908277bed6e1a1c69b277734a7771c2b78633c224445b5c60a9f4bc1d9 \
|
--hash=sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b \
|
||||||
--hash=sha256:4ffc61e8f3bf5b60346d89cd3d37231019c17a081208dfbbd6e1605ba03fa137 \
|
--hash=sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20 \
|
||||||
--hash=sha256:5639c2b16764c6f76eedf722dbad9a0914960d3489c0cc38694ddf9464f1bb2f \
|
--hash=sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044 \
|
||||||
--hash=sha256:58968d331425a6f9eedcee087f77fd3c927c88f55368f43ff7e0a19891f2642c \
|
--hash=sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3 \
|
||||||
--hash=sha256:5d186f32e52e66994dce4f766884bcb9c68b8da62d61d9d215bfe5fb56d21334 \
|
--hash=sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715 \
|
||||||
--hash=sha256:5d20cc348cca3a8aa7312f42ab953a56e15323800ca3ab0706b8cd452a3a056c \
|
--hash=sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4 \
|
||||||
--hash=sha256:6866df152b581f9429020320e5eb9794c8780e90f7ccb021940d7f50ee00ae0b \
|
--hash=sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506 \
|
||||||
--hash=sha256:7d5fe7195c27c32a64955740b949070f21cba664604291c298518d2e255931d2 \
|
--hash=sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f \
|
||||||
--hash=sha256:896530bc9107b226f265effa7ef3f21270f18a2026bc09fed1ebd7b66ddf6375 \
|
--hash=sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0 \
|
||||||
--hash=sha256:962bc30480a08d133e631e8dfd4783ab71cc9e33d5d7c1e192f0b7c06397bb88 \
|
--hash=sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683 \
|
||||||
--hash=sha256:978631ec51a6bbc0b7e58f23b68a8ce9e5f09721940933e9c217068388789fe5 \
|
--hash=sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3 \
|
||||||
--hash=sha256:9b4d4a5dbee05a2c390bf212e78b99434efec37b17a4bff42f50285c5c8c9647 \
|
--hash=sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21 \
|
||||||
--hash=sha256:ab0b005721cc0039e885ac3503825661bd9810b15d4f374e473f8c89b7d5460c \
|
--hash=sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91 \
|
||||||
--hash=sha256:af653022a0c25ef2e3ffb2c673a50e5a0d02fecc41608f4954176f1933b12359 \
|
--hash=sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c \
|
||||||
--hash=sha256:b0cc66c74c797e1db750aaa842ad5b8b78e14805a9b5d1348dc603612d3e3ff5 \
|
--hash=sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8 \
|
||||||
--hash=sha256:b424563394c369a804ecbee9b06dfb34997f19d00b3518e39f83a5642618397d \
|
--hash=sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df \
|
||||||
--hash=sha256:c138abae3a12a94c75c10499f1cbae81294a6f983b3af066390adee73f433028 \
|
--hash=sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c \
|
||||||
--hash=sha256:c6cd67722619e4d55fdb42ead64ed8843d64638e9c07f4011163e46bc512cf01 \
|
--hash=sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb \
|
||||||
--hash=sha256:c91fc8e8fd78af553f98bc7f2a1d8db977334e4eea302a4bfd75b9461c2d8904 \
|
--hash=sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7 \
|
||||||
--hash=sha256:cad399780053fb383dc067475135e41c9fe7d901a97dd5d9c5dfb5611afc0d7d \
|
--hash=sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04 \
|
||||||
--hash=sha256:cb90f60e03d563ca2445099edf605c16ed1d5b15182d21831f58460c48bffb93 \
|
--hash=sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db \
|
||||||
--hash=sha256:dad80b45c22e05b259e33ddd458e9e2ba099c86ccf4e88db7bbab4b747b18d06 \
|
--hash=sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459 \
|
||||||
--hash=sha256:dd3db61b8fe5be220eee484a17233287d0be6932d056cf5738225b9c05ef4fff \
|
--hash=sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea \
|
||||||
--hash=sha256:e28d62e59a4dbd1d22e747f57d4f00c459af22181f0b2f787ea83f5a876d7c76 \
|
--hash=sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914 \
|
||||||
--hash=sha256:e909df4053064a97f1e6565153ff8bb389af12c5c8d29c343308760890560aff \
|
--hash=sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717 \
|
||||||
--hash=sha256:f3ffef566ac88f75967d7abd852ed5f182da252d23fac11b4766da3957766759 \
|
--hash=sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9 \
|
||||||
--hash=sha256:fc3c9babc1e1faefd62704bb46a69f359a9819eb0292e40df3fb6e3574715cd4 \
|
--hash=sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac \
|
||||||
--hash=sha256:fe19d8bc5536a91a24a8133328880a41831b6c5df54599a8417b62fe015d3053
|
--hash=sha256:a23582810fedb8c0bc47524558fb6c56aac3fc252cb306072fd2815da2a47c32 \
|
||||||
|
--hash=sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec \
|
||||||
|
--hash=sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1 \
|
||||||
|
--hash=sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb \
|
||||||
|
--hash=sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac \
|
||||||
|
--hash=sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665 \
|
||||||
|
--hash=sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e \
|
||||||
|
--hash=sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb \
|
||||||
|
--hash=sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5 \
|
||||||
|
--hash=sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936 \
|
||||||
|
--hash=sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de \
|
||||||
|
--hash=sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372 \
|
||||||
|
--hash=sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54 \
|
||||||
|
--hash=sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422 \
|
||||||
|
--hash=sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849 \
|
||||||
|
--hash=sha256:e7aec276d68421f9574040c26e2a7c3771060bc0cff408bae1dcb19d3ab1e63c \
|
||||||
|
--hash=sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963 \
|
||||||
|
--hash=sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018
|
||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# pdfminer-six
|
# pdfminer-six
|
||||||
@@ -382,9 +386,9 @@ distlib==0.4.0 \
|
|||||||
--hash=sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 \
|
--hash=sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 \
|
||||||
--hash=sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d
|
--hash=sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d
|
||||||
# via virtualenv
|
# via virtualenv
|
||||||
django==4.2.26 \
|
django==5.2.8 \
|
||||||
--hash=sha256:9398e487bcb55e3f142cb56d19fbd9a83e15bb03a97edc31f408361ee76d9d7a \
|
--hash=sha256:23254866a5bb9a2cfa6004e8b809ec6246eba4b58a7589bc2772f1bcc8456c7f \
|
||||||
--hash=sha256:c96e64fc3c359d051a6306871bd26243db1bd02317472a62ffdbe6c3cae14280
|
--hash=sha256:37e687f7bd73ddf043e2b6b97cfe02fcbb11f2dbb3adccc6a2b18c6daa054d7f
|
||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# django-slowtests
|
# django-slowtests
|
||||||
@@ -396,25 +400,25 @@ django-querycount==0.8.3 \
|
|||||||
django-slowtests==1.1.1 \
|
django-slowtests==1.1.1 \
|
||||||
--hash=sha256:3c6936d420c9df444ac03625b41d97de043c662bbde61fbcd33e4cd407d0c247
|
--hash=sha256:3c6936d420c9df444ac03625b41d97de043c662bbde61fbcd33e4cd407d0c247
|
||||||
# via -r src/backend/requirements-dev.in
|
# via -r src/backend/requirements-dev.in
|
||||||
django-stubs==5.1.3 \
|
django-stubs==5.2.7 \
|
||||||
--hash=sha256:716758ced158b439213062e52de6df3cff7c586f9f9ad7ab59210efbea5dfe78 \
|
--hash=sha256:2864e74b56ead866ff1365a051f24d852f6ed02238959664f558a6c9601c95bf \
|
||||||
--hash=sha256:8c230bc5bebee6da282ba8a27ad1503c84a0c4cd2f46e63d149e76d2a63e639a
|
--hash=sha256:2a07e47a8a867836a763c6bba8bf3775847b4fd9555bfa940360e32d0ee384a1
|
||||||
# via -r src/backend/requirements-dev.in
|
# via -r src/backend/requirements-dev.in
|
||||||
django-stubs-ext==5.1.3 \
|
django-stubs-ext==5.2.7 \
|
||||||
--hash=sha256:3e60f82337f0d40a362f349bf15539144b96e4ceb4dbd0239be1cd71f6a74ad0 \
|
--hash=sha256:0466a7132587d49c5bbe12082ac9824d117a0dedcad5d0ada75a6e0d3aca6f60 \
|
||||||
--hash=sha256:64561fbc53e963cc1eed2c8eb27e18b8e48dcb90771205180fe29fc8a59e55fd
|
--hash=sha256:b690655bd4cb8a44ae57abb314e0995dc90414280db8f26fff0cb9fb367d1cac
|
||||||
# via django-stubs
|
# via django-stubs
|
||||||
django-test-migrations==1.4.0 \
|
django-test-migrations==1.5.0 \
|
||||||
--hash=sha256:294dff98f6d43d020d4046b971bac5339e7c71458a35e9ad6450c388fe16ed6b \
|
--hash=sha256:1cbff04b1e82c5564a6f635284907b381cc11a2ff883adff46776d9126824f07 \
|
||||||
--hash=sha256:f0c9c92864ed27d0c9a582e92056637e91227f54bd868a50cb9a1726668c563e
|
--hash=sha256:96a08f085fc8bfaa53d44618341d82a2d22fd194c821cd81b147b66f0bec0da8
|
||||||
# via -r src/backend/requirements-dev.in
|
# via -r src/backend/requirements-dev.in
|
||||||
django-types==0.20.0 \
|
django-types==0.22.0 \
|
||||||
--hash=sha256:4e55d2c56155e3d69d75def9eb1d95a891303f2ac19fccf6fe8056da4293fae7 \
|
--hash=sha256:4cecc9eee846e7ff2a398bec9dfe6543e76efb922a7a58c5d6064bcb0e6a3dc5 \
|
||||||
--hash=sha256:a0b5c2c9a1e591684bb21a93b64e50ca6cb2d3eab48f49faff1eac706bd3a9c7
|
--hash=sha256:ba15c756c7a732e58afd0737e54489f1c5e6f1bd24132e9199c637b1f88b057c
|
||||||
# via -r src/backend/requirements-dev.in
|
# via -r src/backend/requirements-dev.in
|
||||||
filelock==3.19.1 \
|
filelock==3.20.0 \
|
||||||
--hash=sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58 \
|
--hash=sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2 \
|
||||||
--hash=sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d
|
--hash=sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4
|
||||||
# via virtualenv
|
# via virtualenv
|
||||||
identify==2.6.15 \
|
identify==2.6.15 \
|
||||||
--hash=sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757 \
|
--hash=sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757 \
|
||||||
@@ -426,16 +430,9 @@ idna==3.11 \
|
|||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# requests
|
# requests
|
||||||
importlib-metadata==8.7.0 \
|
isort==7.0.0 \
|
||||||
--hash=sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000 \
|
--hash=sha256:1bcabac8bc3c36c7fb7b98a76c8abb18e0f841a3ba81decac7691008592499c1 \
|
||||||
--hash=sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd
|
--hash=sha256:5513527951aadb3ac4292a41a16cbc50dd1642432f5e8c20057d414bdafb4187
|
||||||
# via
|
|
||||||
# -c src/backend/requirements.txt
|
|
||||||
# build
|
|
||||||
# isort
|
|
||||||
isort==6.1.0 \
|
|
||||||
--hash=sha256:58d8927ecce74e5087aef019f778d4081a3b6c98f15a80ba35782ca8a2097784 \
|
|
||||||
--hash=sha256:9b8f96a14cfee0677e78e941ff62f03769a06d412aabb9e2a90487b3b7e8d481
|
|
||||||
# via -r src/backend/requirements-dev.in
|
# via -r src/backend/requirements-dev.in
|
||||||
nodeenv==1.9.1 \
|
nodeenv==1.9.1 \
|
||||||
--hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \
|
--hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \
|
||||||
@@ -447,9 +444,9 @@ packaging==25.0 \
|
|||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# build
|
# build
|
||||||
pdfminer-six==20250506 \
|
pdfminer-six==20251107 \
|
||||||
--hash=sha256:b03cc8df09cf3c7aba8246deae52e0bca7ebb112a38895b5e1d4f5dd2b8ca2e7 \
|
--hash=sha256:5fb0c553799c591777f22c0c72b77fc2522d7d10c70654e25f4c5f1fd996e008 \
|
||||||
--hash=sha256:d81ad173f62e5f841b53a8ba63af1a4a355933cfc0ffabd608e568b9193909e3
|
--hash=sha256:c09df33e4cbe6b26b2a79248a4ffcccafaa5c5d39c9fff0e6e81567f165b5401
|
||||||
# via -r src/backend/requirements-dev.in
|
# via -r src/backend/requirements-dev.in
|
||||||
pip==25.3 \
|
pip==25.3 \
|
||||||
--hash=sha256:8d0538dbbd7babbd207f261ed969c65de439f6bc9e5dbd3b3b9a77f25d95f343 \
|
--hash=sha256:8d0538dbbd7babbd207f261ed969c65de439f6bc9e5dbd3b3b9a77f25d95f343 \
|
||||||
@@ -459,15 +456,15 @@ pip-tools==7.5.1 \
|
|||||||
--hash=sha256:a051a94794ba52df9acad2d7c9b0b09ae001617db458a543f8287fea7b89c2cf \
|
--hash=sha256:a051a94794ba52df9acad2d7c9b0b09ae001617db458a543f8287fea7b89c2cf \
|
||||||
--hash=sha256:f5ff803823529edc0e6e40c86b1aa7da7266fb1078093c8beea4e5b77877036a
|
--hash=sha256:f5ff803823529edc0e6e40c86b1aa7da7266fb1078093c8beea4e5b77877036a
|
||||||
# via -r src/backend/requirements-dev.in
|
# via -r src/backend/requirements-dev.in
|
||||||
platformdirs==4.4.0 \
|
platformdirs==4.5.0 \
|
||||||
--hash=sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85 \
|
--hash=sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312 \
|
||||||
--hash=sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf
|
--hash=sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3
|
||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# virtualenv
|
# virtualenv
|
||||||
pre-commit==4.3.0 \
|
pre-commit==4.4.0 \
|
||||||
--hash=sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8 \
|
--hash=sha256:b35ea52957cbf83dcc5d8ee636cbead8624e3a15fbfa61a370e42158ac8a5813 \
|
||||||
--hash=sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16
|
--hash=sha256:f0233ebab440e9f17cabbb558706eb173d19ace965c68cdce2c081042b4fab15
|
||||||
# via -r src/backend/requirements-dev.in
|
# via -r src/backend/requirements-dev.in
|
||||||
pycparser==2.23 \
|
pycparser==2.23 \
|
||||||
--hash=sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2 \
|
--hash=sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2 \
|
||||||
@@ -624,12 +621,7 @@ tomli==2.3.0 \
|
|||||||
--hash=sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf \
|
--hash=sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf \
|
||||||
--hash=sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463 \
|
--hash=sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463 \
|
||||||
--hash=sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876
|
--hash=sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876
|
||||||
# via
|
# via coverage
|
||||||
# -c src/backend/requirements.txt
|
|
||||||
# build
|
|
||||||
# coverage
|
|
||||||
# django-stubs
|
|
||||||
# pip-tools
|
|
||||||
ty==0.0.1a21 \
|
ty==0.0.1a21 \
|
||||||
--hash=sha256:0efba2e52b58f536f4198ba5c4a36cac2ba67d83ec6f429ebc7704233bcda4c3 \
|
--hash=sha256:0efba2e52b58f536f4198ba5c4a36cac2ba67d83ec6f429ebc7704233bcda4c3 \
|
||||||
--hash=sha256:1474d883129bb63da3b2380fc7ead824cd3baf6a9551e6aa476ffefc58057af3 \
|
--hash=sha256:1474d883129bb63da3b2380fc7ead824cd3baf6a9551e6aa476ffefc58057af3 \
|
||||||
@@ -663,28 +655,20 @@ typing-extensions==4.15.0 \
|
|||||||
--hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
|
--hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
|
||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# asgiref
|
|
||||||
# django-stubs
|
# django-stubs
|
||||||
# django-stubs-ext
|
# django-stubs-ext
|
||||||
# django-test-migrations
|
# django-test-migrations
|
||||||
# virtualenv
|
urllib3==2.5.0 \
|
||||||
urllib3==1.26.20 \
|
--hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \
|
||||||
--hash=sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e \
|
--hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc
|
||||||
--hash=sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32
|
|
||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# requests
|
# requests
|
||||||
virtualenv==20.35.3 \
|
virtualenv==20.35.4 \
|
||||||
--hash=sha256:4f1a845d131133bdff10590489610c98c168ff99dc75d6c96853801f7f67af44 \
|
--hash=sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c \
|
||||||
--hash=sha256:63d106565078d8c8d0b206d48080f938a8b25361e19432d2c9db40d2899c810a
|
--hash=sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b
|
||||||
# via pre-commit
|
# via pre-commit
|
||||||
wheel==0.45.1 \
|
wheel==0.45.1 \
|
||||||
--hash=sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729 \
|
--hash=sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729 \
|
||||||
--hash=sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248
|
--hash=sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248
|
||||||
# via pip-tools
|
# via pip-tools
|
||||||
zipp==3.23.0 \
|
|
||||||
--hash=sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e \
|
|
||||||
--hash=sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166
|
|
||||||
# via
|
|
||||||
# -c src/backend/requirements.txt
|
|
||||||
# importlib-metadata
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Please keep this list sorted - if you pin a version provide a reason
|
# Please keep this list sorted - if you pin a version provide a reason
|
||||||
Django<6.0 # Django package
|
django<6.0 # Django package
|
||||||
blessed # CLI for Q Monitor
|
blessed # CLI for Q Monitor
|
||||||
cryptography>=44.0.0 # Core cryptographic functionality
|
cryptography>=44.0.0 # Core cryptographic functionality
|
||||||
django-anymail[amazon_ses,postal] # Email backend for various providers
|
django-anymail[amazon_ses,postal] # Email backend for various providers
|
||||||
|
|||||||
+644
-802
File diff suppressed because it is too large
Load Diff
@@ -134,7 +134,7 @@ test('Spotlight - No Keys', async ({ browser }) => {
|
|||||||
.click();
|
.click();
|
||||||
await page.getByText('License Information').first().waitFor();
|
await page.getByText('License Information').first().waitFor();
|
||||||
await page.getByRole('tab', { name: 'backend Packages' }).waitFor();
|
await page.getByRole('tab', { name: 'backend Packages' }).waitFor();
|
||||||
await page.getByRole('button', { name: 'Django BSD License' }).click();
|
await page.getByRole('button', { name: 'Django BSD-3-Clause' }).click();
|
||||||
|
|
||||||
await page.keyboard.press('Escape');
|
await page.keyboard.press('Escape');
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ def get_installer(content: Optional[dict] = None):
|
|||||||
"""Get the installer for the current environment or a content dict."""
|
"""Get the installer for the current environment or a content dict."""
|
||||||
if content is None:
|
if content is None:
|
||||||
content = dict(os.environ)
|
content = dict(os.environ)
|
||||||
return content.get('INVENTREE_PKG_INSTALLER', None)
|
return content.get('INVENTREE_PKG_INSTALLER')
|
||||||
|
|
||||||
|
|
||||||
# region execution logging helpers
|
# region execution logging helpers
|
||||||
|
|||||||
Reference in New Issue
Block a user