Compare commits

..

37 Commits

Author SHA1 Message Date
Oliver
2f9cf5f1f1 Default Supplier Support Missing in 1.X.X (#10980) (#11027)
Fixes #10979

Co-authored-by: Matthias Mair <code@mjmair.com>
2025-12-17 08:44:27 +11:00
github-actions[bot]
3eb6f12570 Fix for string form fields (#10814) (#10968)
* Fix for string form fields

- replace null values with empty strings

* Expose more serializer metadata

* Check if null values are not allowed

* Fix type

* Try removing feature

* Reduce deltas

* Remove extra field attrs entirely (for testing)

* Comment out changes

* Tweak form values

* Fix for form validation

(cherry picked from commit efc8fb816d)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2025-12-07 00:14:58 +11:00
github-actions[bot]
afc1dad8a7 Obvserve default values for part forms (#10964) (#10965)
- Closes https://github.com/inventree/InvenTree/issues/10909
- Use global setting values as defaults

(cherry picked from commit 3a18934b83)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2025-12-06 20:28:22 +11:00
github-actions[bot]
66b71c1f2e Fixed typo in shebang interpreter directive (#10952) (#10953)
(cherry picked from commit 2ffc2cb9fc)

Co-authored-by: Tyler Tracy <tylertracy@gmail.com>
2025-12-04 12:10:09 +11:00
github-actions[bot]
1a8287824b Allow null values for InvenTreeDecimalField (#10948) (#10951)
- Fixes bug related to importing null "rounding_multiple" BOM field

(cherry picked from commit 7920b0e670)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2025-12-04 07:15:38 +11:00
github-actions[bot]
10769ccb04 [bug] Handle TransactionManagementError (#10942) (#10943)
In the case where we try to call refresh_from_db within an atomic transaction block, it will throw a TransactionManagementError

(cherry picked from commit 38b27271ac)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2025-12-02 17:23:09 +11:00
Oliver
f39b3190e3 Bump software version to 1.1.7 (#10916) 2025-11-26 23:40:40 +11:00
github-actions[bot]
e1a97b2a39 [bug] Stock adjust (#10914) (#10915)
* Extra checks on backend

* Bug fix for adjustment forms

- Set default quantity of zero

* Additional unit testing (to ensure no regression)

(cherry picked from commit 5713cff1cb)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2025-11-26 22:41:36 +11:00
github-actions[bot]
c9a1d9adda Installer missing some required packages from REQS (#10897) (#10898)
Fixes #10813

(cherry picked from commit fcea1383d0)

Co-authored-by: Matthias Mair <code@mjmair.com>
2025-11-24 09:22:53 +11:00
Oliver
64fb5c062a Bump software version to 1.1.6 (#10890) 2025-11-22 22:18:41 +11:00
github-actions[bot]
c09fb48eff [UI] Fix for PartTestResultTable (#10886) (#10889)
* [UI] Fix for PartTestResultTable

- Filter properly by part instance

* Add a simple unit test

* Tweak unit test

(cherry picked from commit e590522909)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2025-11-22 18:02:15 +11:00
Oliver
64a4ac81f2 Bump software version to 1.1.5 (#10884) 2025-11-22 15:47:37 +11:00
Oliver
488a818629 Import fix for UI (#10885) 2025-11-22 14:27:08 +11:00
github-actions[bot]
295c4f3e5d [bug] Serialize location (#10882) (#10883)
* Properly set location id when serializing stock

* Add correct tracking entries

* Add unit test

(cherry picked from commit a7ff1250ba)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2025-11-22 13:44:48 +11:00
github-actions[bot]
c6ecd019dc [UI] Delete stock fix (#10868) (#10869)
* Add helper func getOverviewUrl

* Redirect to parent page when stock item is counted to zero

(cherry picked from commit 468efbacfc)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2025-11-20 06:48:45 +11:00
github-actions[bot]
005d9850b8 Fix for shipping virtual parts (#10853) (#10864)
* Additional checks for virtual parts in sales order process

* Prevent allocation against virtual parts

* Fix order of operations

* Adjust part form fields based on selections

* Prevent order locking

* Updated playwright tests

* Add unit test

(cherry picked from commit 7b38fa30bb)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2025-11-19 15:49:38 +11:00
github-actions[bot]
a585f5407a Bug fix for sales order pricing (#10858) (#10863)
* Bug fix for sales order pricing

- Clear sale price field if no pricing

* Adjust playwright tests

(cherry picked from commit d06d80fb99)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2025-11-19 15:12:16 +11:00
github-actions[bot]
974a7d5510 [UI] Remove duplicate action (#10844) (#10845)
- "Build Output" is same as "stock item" in this case

(cherry picked from commit 57a2de6ffc)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2025-11-17 19:58:54 +11:00
github-actions[bot]
39623ddf98 [UI] Fix for form OPTIONS query (#10840) (#10843)
* [UI] Fix for form OPTIONS query

- Fetch OPTIONs each time form is opened
- Ensure default values are filled correctly
- Prevent issues with latching form state

* Add comment

* Add playwright test

- Check that the reference field increments properly

* Fix other Playwright tests

(cherry picked from commit 770f7a292e)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2025-11-16 22:07:59 +11:00
github-actions[bot]
1890589a43 [bug] State change fixes (#10832) (#10839)
* Fix for setting custom status

* Fix for setting custom status when receiving stock items

* Allow caching for set_status

* Updated code and unit tests

(cherry picked from commit aa9958bf11)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2025-11-15 08:01:21 +11:00
github-actions[bot]
8cbce3f335 Char fix (#10827) (#10830)
* Remove debouncing from text field

* Add debounce to data import field

* Only apply for strings values

* Fix unit test

* More unit test tweaks

(cherry picked from commit ba9b5438b4)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2025-11-14 17:53:27 +11:00
github-actions[bot]
56f09e1aa6 Bug fix for loading boolean settings (#10826) (#10828)
- Do not just cast to bool
- The string "False" casts to True in this case
- Use the function that supports strings

(cherry picked from commit 8cb808f613)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2025-11-14 08:27:42 +11:00
Oliver
a1a2a47bba Bug fix for pack quantity display (#10810) 2025-11-12 00:45:44 +11:00
github-actions[bot]
e2eeaa991d PO receive fix (#10807) (#10808)
* Extract note field when receiving stock items against PO

* Fix tracking entry when receiving item

(cherry picked from commit f3c1cc12af)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2025-11-11 21:15:13 +11:00
github-actions[bot]
4bb1354b68 Fix for pricing display (#10804) (#10805)
(cherry picked from commit 77f80385c9)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2025-11-11 16:27:24 +11:00
Matthias Mair
68e3216b7b [1.1.x] fix(backend): auth check middleware for specific media access (#10784) (#10795)
* fix(backend): auth check middleware for specific media access (#10784)

* simplify

* fix return type

* handle token (app access)

* reduce lookup amount

* add positive test again

* add poisitive test

* move out settings

* add tests for Check2FAMiddleware

* add test for auth_request

* add a reverse name for auth_request

* auth tests refactors

* move test

* disable check for things that do not trigger

* fix typing for python 3.9

* make names clearer and add comments

* finish tests

* fix call

* re-enable mfa test without the timing component

* cleanup helper

* ignore easy out

* ignore scenario that can not happen

(cherry picked from commit f3e8482469)

* fix merge
2025-11-10 09:52:30 +11:00
Matthias Mair
1573d5ff40 Bump software version to 1.1.4 (#10792) 2025-11-09 17:08:15 +11:00
github-actions[bot]
89287d56ff chore(deps): bump django from 4.2.25 to 4.2.26 in /src/backend (#10781) (#10790)
* chore(deps): bump django from 4.2.25 to 4.2.26 in /src/backend

Bumps [django](https://github.com/django/django) from 4.2.25 to 4.2.26.
- [Commits](https://github.com/django/django/compare/4.2.25...4.2.26)

---
updated-dependencies:
- dependency-name: django
  dependency-version: 4.2.26
  dependency-type: direct:production
...



* fix style

* also bump docker version

---------




(cherry picked from commit 726e852b7b)

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matthias Mair <code@mjmair.com>
2025-11-08 22:10:19 +01:00
Oliver
1885caa744 Bump InvenTree software version to 1.1.3 (#10762) 2025-11-04 11:00:09 +11:00
github-actions[bot]
7e2480c4a2 Auth Improvements (#10752) (#10761)
* Return more detail in MFA failure response

* Reject auth requests for users who are inactive

* Move markdown config out of settings.py

(cherry picked from commit 901846272b)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2025-11-04 10:13:48 +11:00
github-actions[bot]
fd4375357f code (#10751) (#10753)
(cherry picked from commit 39f0054cd5)

Co-authored-by: Lê Hoàng Nam <lehoangnam040@gmail.com>
2025-11-04 07:28:09 +11:00
Oliver
77bc7fd4af Bump InvenTree software version to 1.1.2 (#10747) 2025-11-03 12:35:32 +11:00
Oliver
d3a58ea2b1 Bump InvenTree software version to 1.1.1 (#10745) 2025-11-03 11:14:42 +11:00
github-actions[bot]
555455a9d1 fix: typo ins installer stops script from working (#10744) (#10746)
(cherry picked from commit e1bf67b32c)

Co-authored-by: Matthias Mair <code@mjmair.com>
2025-11-03 00:43:11 +01:00
github-actions[bot]
e834ea8aa7 Docs fix (#10738) (#10739)
* Fix heading in helpers.md

* Helper func docs

(cherry picked from commit 46615e447b)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2025-11-03 10:30:08 +11:00
github-actions[bot]
e23e3730b2 fix uv (#10742) (#10743)
https://github.com/inventree/InvenTree/security/dependabot/248
(cherry picked from commit d7daf660ef)

Co-authored-by: Matthias Mair <code@mjmair.com>
2025-11-03 10:24:45 +11:00
Oliver
34436f933d Bump version number to 1.1.0 (#10733)
* Bump version number to 1.1.0

* Add release tag to CHANGELOG.md

* Cleanup

* Remove links
2025-11-02 13:18:23 +11:00
402 changed files with 143030 additions and 163148 deletions

View File

@@ -14,7 +14,7 @@ pool:
strategy:
matrix:
Python39:
PYTHON_VERSION: '3.11'
PYTHON_VERSION: '3.9'
maxParallel: 3
steps:

View File

@@ -4,8 +4,6 @@ updates:
directory: /
schedule:
interval: weekly
cooldown:
default-days: 7
groups:
dependencies:
patterns:
@@ -15,15 +13,11 @@ updates:
directory: /contrib/container
schedule:
interval: weekly
cooldown:
default-days: 7
- package-ecosystem: docker
directory: /.devcontainer
schedule:
interval: weekly
cooldown:
default-days: 7
- package-ecosystem: pip
directories:
@@ -34,8 +28,6 @@ updates:
schedule:
interval: weekly
day: friday
cooldown:
default-days: 7
groups:
dependencies:
patterns:
@@ -49,8 +41,6 @@ updates:
- /src/frontend
schedule:
interval: weekly
cooldown:
default-days: 7
groups:
dependencies:
patterns:

View File

@@ -9,7 +9,7 @@ on:
- l10
env:
python_version: 3.11
python_version: 3.9
permissions:
contents: read
@@ -31,7 +31,7 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false

View File

@@ -39,7 +39,7 @@ jobs:
docker: ${{ steps.filter.outputs.docker }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # pin@v3.0.2
@@ -67,7 +67,7 @@ jobs:
steps:
- name: Check out repo
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false
- name: Test Docker Image
@@ -129,7 +129,7 @@ jobs:
steps:
- name: Check out repo
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false
- name: Run Migration Tests
@@ -153,11 +153,11 @@ jobs:
steps:
- name: Check out repo
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false
- name: Set Up Python ${{ env.python_version }}
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # pin@v6.1.0
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # pin@v6.0.0
with:
python-version: ${{ env.python_version }}
- name: Version Check
@@ -168,7 +168,7 @@ jobs:
echo "git_commit_date=$(git show -s --format=%ci)" >> $GITHUB_ENV
- name: Set up QEMU
if: github.event_name != 'pull_request'
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # pin@v3.7.0
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # pin@v3.6.0
- name: Set up Docker Buildx
if: github.event_name != 'pull_request'
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # pin@v3.11.1
@@ -201,7 +201,7 @@ jobs:
- name: Extract Docker metadata
if: github.event_name != 'pull_request'
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # pin@v5.10.0
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # pin@v5.8.0
with:
images: |
inventree/inventree

View File

@@ -9,7 +9,7 @@ on:
branches-ignore: ["l10*"]
env:
python_version: 3.11
python_version: 3.9
node_version: 20
# The OS version must be set per job
server_start_sleep: 60
@@ -41,7 +41,7 @@ jobs:
requirements: ${{ steps.filter.outputs.requirements }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # pin@v3.0.2
@@ -82,11 +82,11 @@ jobs:
if: needs.paths-filter.outputs.cicd == 'true' || needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.frontend == 'true' || needs.paths-filter.outputs.requirements == 'true' || needs.paths-filter.outputs.force == 'true'
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false
- name: Set up Python ${{ env.python_version }}
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # pin@v6.1.0
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # pin@v6.0.0
with:
python-version: ${{ env.python_version }}
cache: "pip"
@@ -104,7 +104,7 @@ jobs:
if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.requirements == 'true' || needs.paths-filter.outputs.force == 'true'
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false
- name: Environment Setup
@@ -126,11 +126,11 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false
- name: Set up Python ${{ env.python_version }}
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # pin@v6.1.0
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # pin@v6.0.0
with:
python-version: ${{ env.python_version }}
- name: Check Config
@@ -164,7 +164,7 @@ jobs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false
- name: Environment Setup
@@ -249,7 +249,7 @@ jobs:
version: ${{ needs.schema.outputs.version }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
name: Checkout Code
with:
repository: inventree/schema
@@ -302,9 +302,9 @@ jobs:
INVENTREE_LOG_LEVEL: WARNING
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false
persist-credentials: true
- name: Environment Setup
uses: ./.github/actions/setup
with:
@@ -334,8 +334,8 @@ jobs:
continue-on-error: true # continue if a step fails so that coverage gets pushed
strategy:
matrix:
python_version: [3.11]
# python_version: [3.11, 3.14] # Disabled due to requirement issues
python_version: [3.9]
# python_version: [3.9, 3.12] # Disabled due to requirement issues
env:
INVENTREE_DB_NAME: ./inventree.sqlite
@@ -346,7 +346,7 @@ jobs:
python_version: ${{ matrix.python_version }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false
- name: Environment Setup
@@ -377,39 +377,6 @@ jobs:
slug: inventree/InvenTree
flags: backend
performance:
name: Tests - Performance
runs-on: ubuntu-24.04
needs: ["pre-commit", "paths-filter"]
if: needs.paths-filter.outputs.server == 'true' || needs.paths-filter.outputs.force == 'true'
permissions:
contents: read
id-token: write
env:
INVENTREE_DB_NAME: inventree_unit_test_db.sqlite
INVENTREE_DB_ENGINE: sqlite3
INVENTREE_PLUGINS_ENABLED: true
INVENTREE_CONSOLE_LOG: false
INVENTREE_AUTO_UPDATE: true
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
with:
persist-credentials: false
- name: Environment Setup
uses: ./.github/actions/setup
with:
apt-dependency: gettext poppler-utils
dev-install: true
update: true
- name: Performance Reporting
uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # pin@v4
with:
mode: simulation
run: inv dev.test --pytest
postgres:
name: Tests - DB [PostgreSQL]
runs-on: ubuntu-24.04
@@ -430,7 +397,7 @@ jobs:
services:
postgres:
image: postgres:17
image: postgres:14
env:
POSTGRES_USER: inventree
POSTGRES_PASSWORD: password
@@ -443,7 +410,7 @@ jobs:
- 6379:6379
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false
- name: Environment Setup
@@ -491,7 +458,7 @@ jobs:
- 3306:3306
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false
- name: Environment Setup
@@ -525,7 +492,7 @@ jobs:
services:
postgres:
image: postgres:17
image: postgres:14
env:
POSTGRES_USER: inventree
POSTGRES_PASSWORD: password
@@ -533,7 +500,7 @@ jobs:
- 5432:5432
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false
- name: Environment Setup
@@ -567,7 +534,7 @@ jobs:
INVENTREE_PLUGINS_ENABLED: false
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false
name: Checkout Code
@@ -621,7 +588,7 @@ jobs:
if: needs.paths-filter.outputs.frontend == 'true' || needs.paths-filter.outputs.force == 'true'
services:
postgres:
image: postgres:17
image: postgres:15
env:
POSTGRES_DB: inventree
POSTGRES_USER: inventree_user
@@ -646,7 +613,7 @@ jobs:
VITE_COVERAGE_BUILD: true
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false
- name: Environment Setup
@@ -696,7 +663,7 @@ jobs:
timeout-minutes: 60
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false
- name: Environment Setup
@@ -729,7 +696,7 @@ jobs:
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false
- uses: hynek/setup-cached-uv@757bedc3f972eb7227a1aa657651f15a8527c817 # pin@v2
@@ -738,7 +705,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # pin@v3
uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # pin@v3
with:
sarif_file: results.sarif
category: zizmor

View File

@@ -7,7 +7,7 @@ on:
permissions:
contents: read
env:
python_version: 3.11
python_version: 3.9
jobs:
stable:
@@ -20,7 +20,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Checkout Code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false
- name: Version Check
@@ -43,7 +43,7 @@ jobs:
contents: write
attestations: write
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false
- name: Environment Setup
@@ -55,7 +55,7 @@ jobs:
- name: Build frontend
run: cd src/frontend && npm run compile && npm run build
- name: Create SBOM for frontend
uses: anchore/sbom-action@fbfd9c6c189226748411491745178e0c2017392d # pin@v0
uses: anchore/sbom-action@8e94d75ddd33f69f691467e42275782e4bfefe84 # pin@v0
with:
artifact-name: frontend-build.spdx
path: src/frontend
@@ -76,7 +76,7 @@ jobs:
subject-path: "${{ github.workspace }}/src/backend/InvenTree/web/static/frontend-build.zip"
- name: Upload frontend
uses: svenstaro/upload-release-action@6b7fa9f267e90b50a19fef07b3596790bb941741 # pin@2.11.3
uses: svenstaro/upload-release-action@81c65b7cd4de9b2570615ce3aad67a41de5b1a13 # pin@2.11.2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: src/backend/InvenTree/web/static/frontend-build.zip
@@ -84,7 +84,7 @@ jobs:
tag: ${{ github.ref }}
overwrite: true
- name: Upload Attestation
uses: svenstaro/upload-release-action@6b7fa9f267e90b50a19fef07b3596790bb941741 # pin@2.11.3
uses: svenstaro/upload-release-action@81c65b7cd4de9b2570615ce3aad67a41de5b1a13 # pin@2.11.2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
asset_name: frontend-build.intoto.jsonl
@@ -107,7 +107,7 @@ jobs:
INVENTREE_DEBUG: true
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false
- name: Environment Setup
@@ -127,7 +127,7 @@ jobs:
cd docs/site
zip -r docs-html.zip *
- name: Publish documentation
uses: svenstaro/upload-release-action@6b7fa9f267e90b50a19fef07b3596790bb941741 # pin@2.11.3
uses: svenstaro/upload-release-action@81c65b7cd4de9b2570615ce3aad67a41de5b1a13 # pin@2.11.2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: docs/site/docs-html.zip

View File

@@ -32,7 +32,7 @@ jobs:
steps:
- name: "Checkout code"
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
@@ -67,6 +67,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0
with:
sarif_file: results.sarif

View File

@@ -16,7 +16,7 @@ jobs:
pull-requests: write
steps:
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # pin@v10.1.1
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # pin@v10.1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: "This issue seems stale. Please react to show this is still important."

View File

@@ -6,7 +6,7 @@ on:
- master
env:
python_version: 3.11
python_version: 3.9
node_version: 20
permissions:
@@ -32,9 +32,9 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0
with:
persist-credentials: false
persist-credentials: true
- name: Environment Setup
uses: ./.github/actions/setup
with:
@@ -56,7 +56,7 @@ jobs:
echo "Resetting to HEAD~"
git reset HEAD~ || true
- name: crowdin action
uses: crowdin/github-action@60debf382ee245b21794321190ad0501db89d8c1 # pin@v2
uses: crowdin/github-action@08713f00a50548bfe39b37e8f44afb53e7a802d4 # pin@v2
with:
upload_sources: true
upload_translations: false

8
.gitignore vendored
View File

@@ -37,11 +37,6 @@ local_settings.py
*.backup
*.old
# Files generated by profiling tools
*.prof
*.log
*.sql
# Files used for testing
inventree-demo-dataset/
inventree-data/
@@ -116,6 +111,3 @@ api.yaml
# web frontend (static files)
src/backend/InvenTree/web/static
InvenTree/web/static
# performance test results
.codspeed/

View File

@@ -20,9 +20,9 @@ before:
- contrib/packager.io/before.sh
dependencies:
- curl
- "python3.11 | python3.12 | python3.13 | python3.14"
- "python3.11-venv | python3.12-venv | python3.13-venv | python3.14-venv"
- "python3.11-dev | python3.12-dev | python3.13-dev | python3.14-dev"
- "python3.9 | python3.10 | 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.9-dev | python3.10-dev | python3.11-dev | python3.12-dev | python3.13-dev | python3.14-dev"
- python3-pip
- python3-cffi
- python3-brotli
@@ -35,6 +35,8 @@ dependencies:
- jq
- "libffi7 | libffi8"
targets:
ubuntu-20.04: true
ubuntu-22.04: true
ubuntu-24.04: true
debian-11: true
debian-12: true

View File

@@ -10,7 +10,7 @@ exclude: |
)$
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
@@ -18,18 +18,18 @@ repos:
exclude: mkdocs.yml
- id: mixed-line-ending
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.8
rev: v0.11.13
hooks:
- id: ruff-format
args: [--preview]
- id: ruff-check
- id: ruff
args: [
--fix,
# --unsafe-fixes,
--preview
]
- repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.9.16
rev: 0.7.12
hooks:
- id: pip-compile
name: pip-compile requirements-dev.in
@@ -71,16 +71,16 @@ repos:
src/frontend/vite.config.ts |
)$
- repo: https://github.com/biomejs/pre-commit
rev: v2.3.8
rev: v2.0.0-beta.5
hooks:
- id: biome-check
additional_dependencies: ["@biomejs/biome@1.9.4"]
files: ^src/frontend/.*\.(js|ts|tsx)$
- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.0
rev: v8.27.2
hooks:
- id: gitleaks
language_version: 1.25.4
language_version: 1.23.6
#- repo: https://github.com/jumanjihouse/pre-commit-hooks
# rev: 3.0.0
# hooks:

7
.vscode/launch.json vendored
View File

@@ -11,7 +11,7 @@
"program": "${workspaceFolder}/src/backend/InvenTree/manage.py",
"args": [
"runserver",
"0.0.0.0:8000", // expose server in network (useful for testing with mobile app)
// "0.0.0.0:8000", // expose server in network (useful for testing with mobile app)
// "--noreload" // disable auto-reload
],
"django": true,
@@ -35,8 +35,7 @@
"request": "launch",
"program": "${workspaceFolder}/src/backend/InvenTree/manage.py",
"args": [
"runserver",
"0.0.0.0:8000"
"runserver"
],
"django": true,
"justMyCode": false
@@ -45,7 +44,7 @@
"name": "InvenTree invoke schema",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/.venv/lib/python3.11/site-packages/invoke/__main__.py",
"program": "${workspaceFolder}/.venv/lib/python3.9/site-packages/invoke/__main__.py",
"cwd": "${workspaceFolder}",
"args": [
"dev.schema","--ignore-warnings"

View File

@@ -5,29 +5,6 @@ All notable changes to this project will be documented in this file (starting wi
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased - YYYY-MM-DD
### Breaking Changes
- [#10699](https://github.com/inventree/InvenTree/pull/10699) removes the `PartParameter` and `PartParameterTempalate` models (and associated API endpoints). These have been replaced with generic `Parameter` and `ParameterTemplate` models (and API endpoints). Any external client applications which made use of the old endpoints will need to be updated.
### Added
- Adds "Category" columns to BOM and Build Item tables and APIs in [#10722](https://github.com/inventree/InvenTree/pull/10772)
- Adds generic "Parameter" and "ParameterTemplate" models (and associated API endpoints) in [#10699](https://github.com/inventree/InvenTree/pull/10699)
- Adds parameter support for multiple new model types in [#10699](https://github.com/inventree/InvenTree/pull/10699)
- Allows report generator to produce PDF input controls in [#10969](https://github.com/inventree/InvenTree/pull/10969)
- UI overhaul of parameter management in [#10699](https://github.com/inventree/InvenTree/pull/10699)
### Changed
-
### Removed
- Removed python 3.9 / 3.10 support as part of Django 5.2 upgrade in [#10730](https://github.com/inventree/InvenTree/pull/10730)
- Removed the "PartParameter" and "PartParameterTemplate" models (and associated API endpoints) in [#10699](https://github.com/inventree/InvenTree/pull/10699)
- Removed the "ManufacturerPartParameter" model (and associated API endpoints) [#10699](https://github.com/inventree/InvenTree/pull/10699)
## 1.1.0 - 2025-11-02
### Added

View File

@@ -149,7 +149,7 @@ COPY --from=builder_stage ${INVENTREE_BACKEND_DIR}/InvenTree/web/static/web ${IN
COPY --from=builder_stage /root/.local /root/.local
# Launch the production server
CMD ["sh", "-c", "exec gunicorn -c ./gunicorn.conf.py InvenTree.wsgi -b ${INVENTREE_WEB_ADDR}:${INVENTREE_WEB_PORT} --chdir ${INVENTREE_BACKEND_DIR}/InvenTree"]
CMD ["sh", "-c", "exec gunicorn -c ./gunicorn.conf.py InvenTree.wsgi -b 0.0.0.0:8000 --chdir ${INVENTREE_BACKEND_DIR}/InvenTree"]
FROM builder_stage AS dev

View File

@@ -17,7 +17,7 @@ gunicorn>=22.0.0
# LDAP required packages
django-auth-ldap # Django integration for ldap auth
python-ldap # LDAP auth support
django<6.0 # Force lower to match main project
django<5.0 # Force lower to match main project
# Upgraded python package installer
uv

View File

@@ -4,9 +4,9 @@ asgiref==3.10.0 \
--hash=sha256:aef8a81283a34d0ab31630c9b7dfe70c812c95eba78171367ca8745e88124734 \
--hash=sha256:d89f2d8cd8b56dada7d52fa7dc8075baa08fb836560710d38c292a7a3f78c04e
# via django
django==5.2.9 \
--hash=sha256:16b5ccfc5e8c27e6c0561af551d2ea32852d7352c67d452ae3e76b4f6b2ca495 \
--hash=sha256:3a4ea88a70370557ab1930b332fd2887a9f48654261cdffda663fef5976bb00a
django==4.2.26 \
--hash=sha256:9398e487bcb55e3f142cb56d19fbd9a83e15bb03a97edc31f408361ee76d9d7a \
--hash=sha256:c96e64fc3c359d051a6306871bd26243db1bd02317472a62ffdbe6c3cae14280
# via
# -r contrib/container/requirements.in
# django-auth-ldap
@@ -53,77 +53,77 @@ packaging==25.0 \
# via
# gunicorn
# mariadb
psycopg[binary, pool]==3.2.12 \
--hash=sha256:85c08d6f6e2a897b16280e0ff6406bef29b1327c045db06d21f364d7cd5da90b \
--hash=sha256:8a1611a2d4c16ae37eada46438be9029a35bb959bb50b3d0e1e93c0f3d54c9ee
psycopg[binary, pool]==3.2.11 \
--hash=sha256:217231b2b6b72fba88281b94241b2f16043ee67f81def47c52a01b72ff0c086a \
--hash=sha256:398bb484ed44361e041c8f804ed7af3d2fcefbffdace1d905b7446c319321706
# via -r contrib/container/requirements.in
psycopg-binary==3.2.12 \
--hash=sha256:095ccda59042a1239ac2fefe693a336cb5cecf8944a8d9e98b07f07e94e2b78d \
--hash=sha256:0afb71a99871a41dd677d207c6a988d978edde5d6a018bafaed4f9da45357055 \
--hash=sha256:100fdfee763d701f6da694bde711e264aca4c2bc84fb81e1669fb491ce11d219 \
--hash=sha256:13cd057f406d2c8063ae8b489395b089a7f23c39aff223b5ea39f0c4dd640550 \
--hash=sha256:15e226f0d8af85cc8b2435b2e9bc6f0d40febc79eef76cf20fceac4d902a6a7b \
--hash=sha256:16db2549a31ccd4887bef05570d95036813ce25fd9810b523ba1c16b0f6cfd90 \
--hash=sha256:1c1dbeb8e97d00a33dfa9987776ce3d1c1e4cc251dfbd663b8f9e173f5c89d17 \
--hash=sha256:1d7cedecbe0bb60a2e72b1613fba4072a184a6472d6cc9aa99e540217f544e3e \
--hash=sha256:2598d0e4f2f258da13df0560187b3f1dfc9b8688c46b9d90176360ae5212c3fc \
--hash=sha256:26b5927b5880b396231ab6190ee5c8fb47ed3f459b53504ed5419faaf16d3bfb \
--hash=sha256:294f08b014f08dfd3c9b72408f5e1a0fd187bd86d7a85ead651e32dbd47aa038 \
--hash=sha256:2aa80ca8d17266507bef853cecefa7d632ffd087883ee7ca92b8a7ea14a1e581 \
--hash=sha256:2d55009eeddbef54c711093c986daaf361d2c4210aaa1ee905075a3b97a62441 \
--hash=sha256:310c95a68a9b948b89d6d187622757d57b6c26cece3c3f7c2cbb645ee36531b2 \
--hash=sha256:32b3e12d9441508f9c4e1424f4478b1a518a90a087cd54be3754e74954934194 \
--hash=sha256:356b4266e5cde7b5bbcf232f549dedf7fbed4983daa556042bdec397780e044d \
--hash=sha256:385c7b5cfffac115f413b8e32c941c85ea0960e0b94a6ef43bb260f774c54893 \
--hash=sha256:3c1e38b1eda54910628f68448598139a9818973755abf77950057372c1fe89a6 \
--hash=sha256:3e9c9e64fb7cda688e9488402611c0be2c81083664117edcc709d15f37faa30f \
--hash=sha256:442f20153415f374ae5753ca618637611a41a3c58c56d16ce55f845d76a3cf7b \
--hash=sha256:489b154891f1c995355adeb1077ee3479e9c9bada721b93270c20243bbad6542 \
--hash=sha256:48a8e29f3e38fcf8d393b8fe460d83e39c107ad7e5e61cd3858a7569e0554a39 \
--hash=sha256:49582c3b6d578bdaab2932b59f70b1bd93351ed4d594b2c97cea1611633c9de1 \
--hash=sha256:58ed30d33c25d7dc8d2f06285e88493147c2a660cc94713e4b563a99efb80a1f \
--hash=sha256:5b6e505618cb376a7a7d6af86833a8f289833fe4cc97541d7100745081dc31bd \
--hash=sha256:66a031f22e4418016990446d3e38143826f03ad811b9f78f58e2afbc1d343f7a \
--hash=sha256:6a898717ab560db393355c6ecf39b8c534f252afc3131480db1251e061090d3a \
--hash=sha256:7130effd0517881f3a852eff98729d51034128f0737f64f0d1c7ea8343d77bd7 \
--hash=sha256:72fd979e410ba7805462817ef8ed6f37dd75f9f4ae109bdb8503e013ccecb80b \
--hash=sha256:77690f0bf08356ca00fc357f50a5980c7a25f076c2c1f37d9d775a278234fefd \
--hash=sha256:79de3cc5adbf51677009a8fda35ac9e9e3686d5595ab4b0c43ec7099ece6aeb5 \
--hash=sha256:7b9a99ded7d19b24d3b6fa632b58e52bbdecde7e1f866c3b23d0c27b092af4e3 \
--hash=sha256:802bd01fb18a0acb0dea491f69a9a2da6034f33329a62876ab5b558a1fb66b45 \
--hash=sha256:8335d989a4e94df2ccd8a1acbba9d03c4157ea8d73b65b79d447c6dc10b001d8 \
--hash=sha256:89b3c5201ca616d69ca0c3c0003ca18f7170a679c445c7e386ebfb4f29aa738e \
--hash=sha256:8ffe75fe6be902dadd439adf4228c98138a992088e073ede6dd34e7235f4e03e \
--hash=sha256:909de94de7dd4d6086098a5755562207114c9638ec42c52d84c8a440c45fe084 \
--hash=sha256:940ac69ef6e89c17b3d30f3297a2ad03efdd06a4b1857f81bc533a9108a90eb9 \
--hash=sha256:95f2806097a49bfd57e0c6a178f77b99487c53c157d9d507aee9c40dd58efdb4 \
--hash=sha256:9c674887d1e0d4384c06c822bc7fcfede4952742e232ec1e76b5a6ae39a3ddd4 \
--hash=sha256:9fdf3a0c24822401c60c93640da69b3dfd4d9f29c3a8d797244fe22bfe592823 \
--hash=sha256:ab02b7d138768fd6ac4230e45b073f7b9fd688d88c04f24c34df4a250a94d066 \
--hash=sha256:acb1811219a4144539f0baee224a11a2aa323a739c349799cf52f191eb87bc52 \
--hash=sha256:bfd632f7038c76b0921f6d5621f5ba9ecabfad3042fa40e5875db11771d2a5de \
--hash=sha256:ce68839da386f137bc8d814fdbeede8f89916b8605e3593a85b504a859243af9 \
--hash=sha256:d369e79ad9647fc8217cbb51bbbf11f9a1ffca450be31d005340157ffe8e91b3 \
--hash=sha256:dc68094e00a5a7e8c20de1d3a0d5e404a27f522e18f8eb62bbbc9f865c3c81ef \
--hash=sha256:deeb06b7141f3a577c3aa8562307e2747580ae43d705a0482603a2c1f110d046 \
--hash=sha256:e0b5ccd03ca4749b8f66f38608ccbcb415cbd130d02de5eda80d042b83bee90e \
--hash=sha256:ea049c8d33c4f4e6b030d5a68123c0ccd2ffb77d4035f073db97187b49b6422f \
--hash=sha256:ea9751310b840186379c949ede5a5129b31439acdb929f3003a8685372117ed8 \
--hash=sha256:ec82fa5134517af44e28a30c38f34384773a0422ffd545fd298433ea9f2cc5a9 \
--hash=sha256:eedc410f82007038030650aa58f620f9fe0009b9d6b04c3dc71cbd3bae5b2675 \
--hash=sha256:ef40601b959cc1440deaf4d53472ab54fa51036c37189cf3fe5500559ac25347 \
--hash=sha256:ef92d5ba6213de060d1390b1f71f5c3b2fbb00b4d55edee39f3b07234538b64a \
--hash=sha256:efab679a2c7d1bf7d0ec0e1ecb47fe764945eff75bb4321f2e699b30a12db9b3 \
--hash=sha256:f33c9e12ed05e579b7fb3c8fdb10a165f41459394b8eb113e7c377b2bd027f61 \
--hash=sha256:f3bae4be7f6781bf6c9576eedcd5e1bb74468126fa6de991e47cdb1a8ea3a42a \
--hash=sha256:f6ba1fe35fd215813dac4544a5ffc90f13713b29dd26e9e5be97ba53482bf6d6 \
--hash=sha256:f7c81bc60560be9eb3c23601237765069ebfa9881097ce19ca6b5ea17c5faa8f \
--hash=sha256:f8107968a9eadb451cfa6cf86036006fdde32a83cd39c26c9ca46765e653b547 \
--hash=sha256:f821e0c8a8fdfddfa71acb4f462d7a4c5aae1655f3f5e078970dbe9f19027386
psycopg-binary==3.2.11 \
--hash=sha256:00221bfeb9594ca6e01207b032c300fa6f889d918bf0de47f4571c1f9f6e1578 \
--hash=sha256:110a2036007230416fcc2c17bfe7aaa2c1fa9b6e9d21e2cd551523e3f6489759 \
--hash=sha256:199f88a05dd22133eab2deb30348ef7a70c23d706c8e63fdc904234163c63517 \
--hash=sha256:1db270e6bdbd183e3908cd9bb832506b99e1f2222a2fc2145f980c3ba1c8c30f \
--hash=sha256:20d41bcd9ac289d44ac1f6151594f7883483b4ad14680a63e04b639dc90c3349 \
--hash=sha256:23c77dbbffe8ba679213877f7204f4599bd545b65d2d69982fd685a3fea35b11 \
--hash=sha256:260738ae222b41dbefd0d84cb2e150a112f90b41688630f57fdac487ab6d6f38 \
--hash=sha256:27eb6367350b75fef882c40cd6f748bfd976db2f8651f7511956f11efc15154f \
--hash=sha256:2a438fad4cc081b018431fde0e791b6d50201526edf39522a85164f606c39ddb \
--hash=sha256:30e2c114d26554ae677088de5d4133cc112344d7a233200fdbf4a2ca5754c7ec \
--hash=sha256:31f1d5630afa673c37a6327f8e3efa1f17d4e4e42972643b3478b52275233529 \
--hash=sha256:32bd319a68420631a320bb450921c8320641621a92556c97b38b1e116010c344 \
--hash=sha256:3bd2c8fb1dec6f93383fbaa561591fa3d676e079f9cb9889af17c3020a19715f \
--hash=sha256:3f32b09fba85d9e239229bdc5b6254420c02054f6954fe7fbd1ecf1ca93009ed \
--hash=sha256:478a68d50f34f6203642d245e2046d266c719ab4e593a1bb94c3be5f82e1aee1 \
--hash=sha256:47f6cf8a1d02d25238bdb8741ac641ff0ec22b1c6ff6a2acd057d0da5c712842 \
--hash=sha256:49d76391b225f72dd63fcab87937ccf307ae0f093b5a382eeacf05f19a57c176 \
--hash=sha256:4cae9bdc482e36e825d5102a9f3010e729f33a4ca83fc8a1f439ba16eb61e1f1 \
--hash=sha256:54a30f00a51b9043048b3e7ee806ffd31fc5fbd02a20f0e69d21306ff33dc473 \
--hash=sha256:566d02a0b85b994e40b4f6276b3423c59e8157f10b73bd2e634f8e0a3dfb1890 \
--hash=sha256:5768a9e7d393b2edd3a28de5a6d5850d054a016ed711f7044a9072f19f5e50d5 \
--hash=sha256:581358e770a4536e546841b78fd0fe318added4a82443bf22d0bbe3109cf9582 \
--hash=sha256:58997db1aa48a1119e26c1c2f893d1c92339bd3be5d1f25334f22eaeaeeca90e \
--hash=sha256:58d8f9f80ae79ba7f2a0509424939236220d7d66a4f8256ae999b882cc58065b \
--hash=sha256:592fb928efe0674a7400af914bcf931eb5267d36237925947aaecf63bd9a91aa \
--hash=sha256:5bc571786a256a2fa2d8f13b5ecf714020b753bc76c2fa6d308e46751946dc31 \
--hash=sha256:5f6f948ff1cd252003ff534d7b50a2b25453b4212b283a7514ff8751bdb68c37 \
--hash=sha256:5fb27dd9c52ae13cb4de90244207155b694f76a75a816115ead2d573f40e1e36 \
--hash=sha256:6688807ed07436c18e9946d01372bc80b9d20b7732cde27de9313e0860910c84 \
--hash=sha256:6b9632c42f76d5349e7dd50025cff02688eb760b258e891ad2c6428e7e4917d5 \
--hash=sha256:720e19ff2d1c425b6be18bd20ba35010c7927e492bcfecbae1085a89caa7db7c \
--hash=sha256:749d23fbfd642a7abfef5fc0f6ca185fa82a2c0f895e6eab42c3f2a5d88f6011 \
--hash=sha256:7608c9fa58b85426093ab8777080e8f134d89915c05c51fa270e7aee317f2b38 \
--hash=sha256:766089fdaa8af1b5f7e2ec9fd7ad190c865e226b4fb0e7b1bd8dbcd62b5b923e \
--hash=sha256:7744b4ed1f3b76fe37de7e9ef98014482fe74b6d3dfe1026cc4cfb4b4404e74f \
--hash=sha256:7b3c5474dbad63bcccb8d14d4d4c7c19f1dc6f8e8c1914cbc771d261cf8eddca \
--hash=sha256:81e57d1f00af9b7414c8d00ac77892b3786ddd69a23c27dee47cae8fd3543b07 \
--hash=sha256:82fe30afbdd66fbdad583b02baad5c15930a3dc8a3756d2ae15fc874e9be8ec8 \
--hash=sha256:8792e502a16a0b28d9fd23571fe492271a00c4b2b55f6c0b8377e47032758cd3 \
--hash=sha256:91268f04380964a5e767f8102d05f1e23312ddbe848de1a9514b08b3fc57d354 \
--hash=sha256:9b4b0fc4e774063ae64c92cc57e2b10160150de68c96d71743218159d953869d \
--hash=sha256:9bdc762600fcc8e4ad3224734a4e70cc226207fd8f2de47c36b115efeed01782 \
--hash=sha256:9ea3ebe1706fd78d6ac0dd1cf692a77cfacd5ba967c82128f9863a5e48f63c47 \
--hash=sha256:9f12a34bddaeffa7840a61163595ec0d70a9db855896865dcfbb731510014484 \
--hash=sha256:a3a59d404e1fb8ec47116f66f5adf48a8993a8aac0dad0395a923155fd55ee38 \
--hash=sha256:b051aa1e67f0d03ccdb4503d716f22da56229896526f0aa721e5a199baa9e5d4 \
--hash=sha256:b2fa94ce40bc4b408149d83a6204fc5e53c3e9d3cd5b749de2e7e9671a049cf7 \
--hash=sha256:c45f61202e5691090a697e599997eaffa3ec298209743caa4fd346145acabafe \
--hash=sha256:c594c199869099c59c85b9f4423370b6212491fb929e7fcda0da1768761a2c2c \
--hash=sha256:d27f51b8ce291da4af749ef850adb4520bfe52c2ff4677402c719ff35af03f00 \
--hash=sha256:d59db908d9baaa057a43dd5aa8352f3e3de4b8c57f295172d5fe521e97d6c39d \
--hash=sha256:d7e490848d7bedf6c1d2180233a33d31d554a1b0823f80a0236ebb7d3b6caf12 \
--hash=sha256:e3b6328bc2f3ca233f9a5f08d266089b96a534eca9ee4e45cb92d0a8d4629d9c \
--hash=sha256:e3f5887019dfb094c60e7026968ca3a964ca16305807ba5e43f9a78483767d5f \
--hash=sha256:e7575ca710277cc3e9257ff803a3e0e3cb7cc1b7851639cb783a7cd55ebfc815 \
--hash=sha256:e7f4dff472a529c9027f294c8842ab535bbed7e2928fe1f4fc28b27f2463d6d5 \
--hash=sha256:eab6959fade522e586b8ec37d3fe337ce10861965edef3292f52e66e36dc375d \
--hash=sha256:f5e7415b5d0f58edf2708842c66605092df67f3821161d861b09695fc326c4de \
--hash=sha256:f72146ad5b69ea177c2707578e5a4a9422b79e50d5a80992dabc5619b0929771 \
--hash=sha256:fa2aa5094dc962967ca0978c035b3ef90329b802501ef12a088d3bac6a55598e \
--hash=sha256:fe5e3648e855df4fba1d70c18aef18c9880ea8d123fdfae754c18787c8cb37b3 \
--hash=sha256:ff64883cff66fe797cd958c0ff7f53fc36a28239b9e0dc80189ce1c03ce47153
# via psycopg
psycopg-pool==3.2.7 \
--hash=sha256:4b47bb59d887ef5da522eb63746b9f70e2faf967d34aac4f56ffc65e9606728f \
--hash=sha256:a77d531bfca238e49e5fb5832d65b98e69f2c62bfda3d2d4d833696bdc9ca54b
psycopg-pool==3.2.6 \
--hash=sha256:0f92a7817719517212fbfe2fd58b8c35c1850cdd2a80d36b581ba2085d9148e5 \
--hash=sha256:5887318a9f6af906d041a0b1dc1c60f8f0dda8340c2572b74e10907b51ed5da7
# via psycopg
pyasn1==0.6.1 \
--hash=sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 \
@@ -229,26 +229,26 @@ typing-extensions==4.15.0 \
# via
# psycopg
# psycopg-pool
uv==0.9.8 \
--hash=sha256:0f03bc413c933dbf850ad0dc2dba3df6b80c860a5c65cd767add49da19dadef0 \
--hash=sha256:14670bf55ecb5cfd0f3654fbf51c58a21dec3ad8ab531079b3ed8599271dc77b \
--hash=sha256:1b8b5bdcda3e10ea70b618d0609acddc5c725cb58d4caf933030ddedd7c2e98f \
--hash=sha256:40253d00c1e900a0a61b132b1e0dd4aa83575cfd5302d3671899b6de29b1ef67 \
--hash=sha256:50d130c46d97d7f10675ebea8608b7b4722c84b5745cd1bb0c8ae6d7984c05d5 \
--hash=sha256:543693def38fa41b9706aba391111fe8d9dd6be86899d76f9581faf045ac1cb6 \
--hash=sha256:5af28f1645eb3c50fd34a78508792db2d0799816f4eb5f55e1e6e2c724dfb125 \
--hash=sha256:6a01d7cd41953ffac583139b10ad1df004a67c0246a6b694eb5bcdbc8c99deaf \
--hash=sha256:6df2e16f6df32018047c60bab2c0284868ad5c309addba9183ea2eeb71746bf0 \
--hash=sha256:7038a552159f2291dd0d1f4f66a36261b5f3ed5fcd92e2869186f8e910b2c935 \
--hash=sha256:75671150d6eb9d5ee829e1fdb8cf86b8e44a66d27cbb996fe807e986c4107b5d \
--hash=sha256:87c3b65b6d5fcbdeab199d54c74fbf75de19cb534a690c936c5616478a038576 \
--hash=sha256:99b18bfe92c33c3862b65d74677697e799763e669e0064685f405e7e27517f25 \
--hash=sha256:9f2f3576c4518ff4f15e48dbca70585a513523c4738bc8cc2e48b20fd1190ce3 \
--hash=sha256:a4010b3fdabbb3c4f2cf2f7aa3bf6002d00049dcbc54ce0ee5ada32a933b2290 \
--hash=sha256:bb0f8e83c2a2fc5a802e930cc8a7b71ab068180300a3f27ba38037f9fcb3d430 \
--hash=sha256:cdbfadca9522422ab9820f5ada071c9c5c869bcd6fee719d20d91d5ec85b2a7d \
--hash=sha256:d93a2227d23e81ab3a16c30363559afc483e8aca40ea9343b3f326a9a41718c9 \
--hash=sha256:f52c6a99197028a314d4c1825f7ccb696eb9a88b822d2e2f17046266c75e543e
uv==0.9.7 \
--hash=sha256:0fdbfad5b367e7a3968264af6da5bbfffd4944a90319042f166e8df1a2d9de09 \
--hash=sha256:134e0daac56f9e399ccdfc9e4635bc0a13c234cad9224994c67bae462e07399a \
--hash=sha256:1aaf79b4234400e9e2fbf5b50b091726ccbb0b6d4d032edd3dfd4c9673d89dca \
--hash=sha256:34fe0af83fcafb9e2b786f4bd633a06c878d548a7c479594ffb5607db8778471 \
--hash=sha256:555ee72146b8782c73d755e4a21c9885c6bfc81db0ffca2220d52dddae007eb7 \
--hash=sha256:56a440ccde7624a7bc070e1c2492b358c67aea9b8f17bc243ea27c5871c8d02c \
--hash=sha256:62b315f62669899076a1953fba6baf50bd2b57f66f656280491331dcedd7e6c6 \
--hash=sha256:635e82c2d0d8b001618af82e4f2724350f15814f6462a71b3ebd44adec21f03c \
--hash=sha256:7019f4416925f4091b9d28c1cf3e8444cf910c4ede76bdf1f6b9a56ca5f97985 \
--hash=sha256:777bb1de174319245a35e4f805d3b4484d006ebedae71d3546f95e7c28a5f436 \
--hash=sha256:89697fa0d7384ba047daf75df844ee7800235105e41d08e0c876861a2b4aa90e \
--hash=sha256:8cf6bc2482d1293cc630f66b862b494c09acda9b7faff7307ef52667a2b3ad49 \
--hash=sha256:b5f1fb8203a77853db176000e8f30d5815ab175dc46199db059f97a72fc51110 \
--hash=sha256:bb8bfcc2897f7653522abc2cae80233af756ad857bfbbbbe176f79460cbba417 \
--hash=sha256:bcf878528bd079fe8ae15928b5dfa232fac8b0e1854a2102da6ae1a833c31276 \
--hash=sha256:c9810ee8173dce129c49b338d5e97f3d7c7e9435f73e0b9b26c2f37743d3bb9e \
--hash=sha256:d13da6521d4e841b1e0a9fda82e793dcf8458a323a9e8955f50903479d0bfa97 \
--hash=sha256:d6e5fe28ca05a4b576c0e8da5f69251dc187a67054829cfc4afb2bfa1767114b \
--hash=sha256:edd768f6730bba06aa10fdbd80ee064569f7236806f636bf65b68136a430aad0
# via -r contrib/container/requirements.in
wheel==0.45.1 \
--hash=sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729 \

View File

@@ -214,72 +214,66 @@ ruamel-yaml==0.18.15 \
--hash=sha256:148f6488d698b7a5eded5ea793a025308b25eca97208181b6a026037f391f701 \
--hash=sha256:dbfca74b018c4c3fba0b9cc9ee33e53c371194a9000e694995e620490fd40700
# via jc
ruamel-yaml-clib==0.2.15 \
--hash=sha256:014181cdec565c8745b7cbc4de3bf2cc8ced05183d986e6d1200168e5bb59490 \
--hash=sha256:04d21dc9c57d9608225da28285900762befbb0165ae48482c15d8d4989d4af14 \
--hash=sha256:05c70f7f86be6f7bee53794d80050a28ae7e13e4a0087c1839dcdefd68eb36b6 \
--hash=sha256:0ba6604bbc3dfcef844631932d06a1a4dcac3fee904efccf582261948431628a \
--hash=sha256:11e5499db1ccbc7f4b41f0565e4f799d863ea720e01d3e99fa0b7b5fcd7802c9 \
--hash=sha256:1b45498cc81a4724a2d42273d6cfc243c0547ad7c6b87b4f774cb7bcc131c98d \
--hash=sha256:1bb7b728fd9f405aa00b4a0b17ba3f3b810d0ccc5f77f7373162e9b5f0ff75d5 \
--hash=sha256:1f66f600833af58bea694d5892453f2270695b92200280ee8c625ec5a477eed3 \
--hash=sha256:27dc656e84396e6d687f97c6e65fb284d100483628f02d95464fd731743a4afe \
--hash=sha256:2812ff359ec1f30129b62372e5f22a52936fac13d5d21e70373dbca5d64bb97c \
--hash=sha256:2b216904750889133d9222b7b873c199d48ecbb12912aca78970f84a5aa1a4bc \
--hash=sha256:331fb180858dd8534f0e61aa243b944f25e73a4dae9962bd44c46d1761126bbf \
--hash=sha256:3cb75a3c14f1d6c3c2a94631e362802f70e83e20d1f2b2ef3026c05b415c4900 \
--hash=sha256:3eb199178b08956e5be6288ee0b05b2fb0b5c1f309725ad25d9c6ea7e27f962a \
--hash=sha256:424ead8cef3939d690c4b5c85ef5b52155a231ff8b252961b6516ed7cf05f6aa \
--hash=sha256:45702dfbea1420ba3450bb3dd9a80b33f0badd57539c6aac09f42584303e0db6 \
--hash=sha256:468858e5cbde0198337e6a2a78eda8c3fb148bdf4c6498eaf4bc9ba3f8e780bd \
--hash=sha256:46895c17ead5e22bea5e576f1db7e41cb273e8d062c04a6a49013d9f60996c25 \
--hash=sha256:46e4cc8c43ef6a94885f72512094e482114a8a706d3c555a34ed4b0d20200600 \
--hash=sha256:480894aee0b29752560a9de46c0e5f84a82602f2bc5c6cde8db9a345319acfdf \
--hash=sha256:4b293a37dc97e2b1e8a1aec62792d1e52027087c8eea4fc7b5abd2bdafdd6642 \
--hash=sha256:4be366220090d7c3424ac2b71c90d1044ea34fca8c0b88f250064fd06087e614 \
--hash=sha256:4d1032919280ebc04a80e4fb1e93f7a738129857eaec9448310e638c8bccefcf \
--hash=sha256:4d3b58ab2454b4747442ac76fab66739c72b1e2bb9bd173d7694b9f9dbc9c000 \
--hash=sha256:4dcec721fddbb62e60c2801ba08c87010bd6b700054a09998c4d09c08147b8fb \
--hash=sha256:512571ad41bba04eac7268fe33f7f4742210ca26a81fe0c75357fa682636c690 \
--hash=sha256:542d77b72786a35563f97069b9379ce762944e67055bea293480f7734b2c7e5e \
--hash=sha256:56ea19c157ed8c74b6be51b5fa1c3aff6e289a041575f0556f66e5fb848bb137 \
--hash=sha256:5d3c9210219cbc0f22706f19b154c9a798ff65a6beeafbf77fc9c057ec806f7d \
--hash=sha256:5fea0932358e18293407feb921d4f4457db837b67ec1837f87074667449f9401 \
--hash=sha256:617d35dc765715fa86f8c3ccdae1e4229055832c452d4ec20856136acc75053f \
--hash=sha256:64da03cbe93c1e91af133f5bec37fd24d0d4ba2418eaf970d7166b0a26a148a2 \
--hash=sha256:65f48245279f9bb301d1276f9679b82e4c080a1ae25e679f682ac62446fac471 \
--hash=sha256:6f1d38cbe622039d111b69e9ca945e7e3efebb30ba998867908773183357f3ed \
--hash=sha256:713cd68af9dfbe0bb588e144a61aad8dcc00ef92a82d2e87183ca662d242f524 \
--hash=sha256:71845d377c7a47afc6592aacfea738cc8a7e876d586dfba814501d8c53c1ba60 \
--hash=sha256:753faf20b3a5906faf1fc50e4ddb8c074cb9b251e00b14c18b28492f933ac8ef \
--hash=sha256:7e74ea87307303ba91073b63e67f2c667e93f05a8c63079ee5b7a5c8d0d7b043 \
--hash=sha256:88eea8baf72f0ccf232c22124d122a7f26e8a24110a0273d9bcddcb0f7e1fa03 \
--hash=sha256:923816815974425fbb1f1bf57e85eca6e14d8adc313c66db21c094927ad01815 \
--hash=sha256:9b6f7d74d094d1f3a4e157278da97752f16ee230080ae331fcc219056ca54f77 \
--hash=sha256:a8220fd4c6f98485e97aea65e1df76d4fed1678ede1fe1d0eed2957230d287c4 \
--hash=sha256:ab0df0648d86a7ecbd9c632e8f8d6b21bb21b5fc9d9e095c796cacf32a728d2d \
--hash=sha256:ac9b8d5fa4bb7fd2917ab5027f60d4234345fd366fe39aa711d5dca090aa1467 \
--hash=sha256:badd1d7283f3e5894779a6ea8944cc765138b96804496c91812b2829f70e18a7 \
--hash=sha256:bdc06ad71173b915167702f55d0f3f027fc61abd975bd308a0968c02db4a4c3e \
--hash=sha256:bf0846d629e160223805db9fe8cc7aec16aaa11a07310c50c8c7164efa440aec \
--hash=sha256:bfd309b316228acecfa30670c3887dcedf9b7a44ea39e2101e75d2654522acd4 \
--hash=sha256:c583229f336682b7212a43d2fa32c30e643d3076178fb9f7a6a14dde85a2d8bd \
--hash=sha256:cb15a2e2a90c8475df45c0949793af1ff413acfb0a716b8b94e488ea95ce7cff \
--hash=sha256:d290eda8f6ada19e1771b54e5706b8f9807e6bb08e873900d5ba114ced13e02c \
--hash=sha256:da3d6adadcf55a93c214d23941aef4abfd45652110aed6580e814152f385b862 \
--hash=sha256:dcc7f3162d3711fd5d52e2267e44636e3e566d1e5675a5f0b30e98f2c4af7974 \
--hash=sha256:def5663361f6771b18646620fca12968aae730132e104688766cf8a3b1d65922 \
--hash=sha256:e5e9f630c73a490b758bf14d859a39f375e6999aea5ddd2e2e9da89b9953486a \
--hash=sha256:e9fde97ecb7bb9c41261c2ce0da10323e9227555c674989f8d9eb7572fc2098d \
--hash=sha256:ef71831bd61fbdb7aa0399d5c4da06bea37107ab5c79ff884cc07f2450910262 \
--hash=sha256:f4421ab780c37210a07d138e56dd4b51f8642187cdfb433eb687fe8c11de0144 \
--hash=sha256:f6d3655e95a80325b84c4e14c080b2470fe4f33b6846f288379ce36154993fb1 \
--hash=sha256:fd4c928ddf6bce586285daa6d90680b9c291cfd045fc40aad34e445d57b1bf51 \
--hash=sha256:fe239bdfdae2302e93bd6e8264bd9b71290218fff7084a9db250b55caaccf43f
ruamel-yaml-clib==0.2.14 \
--hash=sha256:090782b5fb9d98df96509eecdbcaffd037d47389a89492320280d52f91330d78 \
--hash=sha256:0a54e5e40a7a691a426c2703b09b0d61a14294d25cfacc00631aa6f9c964df0d \
--hash=sha256:10d9595b6a19778f3269399eff6bab642608e5966183abc2adbe558a42d4efc9 \
--hash=sha256:16a60d69f4057ad9a92f3444e2367c08490daed6428291aa16cefb445c29b0e9 \
--hash=sha256:18c041b28f3456ddef1f1951d4492dbebe0f8114157c1b3c981a4611c2020792 \
--hash=sha256:1c1acc3a0209ea9042cc3cfc0790edd2eddd431a2ec3f8283d081e4d5018571e \
--hash=sha256:1f118b707eece8cf84ecbc3e3ec94d9db879d85ed608f95870d39b2d2efa5dca \
--hash=sha256:2070bf0ad1540d5c77a664de07ebcc45eebd1ddcab71a7a06f26936920692beb \
--hash=sha256:26a8de280ab0d22b6e3ec745b4a5a07151a0f74aad92dd76ab9c8d8d7087720d \
--hash=sha256:275f938692013a3883edbd848edde6d9f26825d65c9a2eb1db8baa1adc96a05d \
--hash=sha256:27c070cf3888e90d992be75dd47292ff9aa17dafd36492812a6a304a1aedc182 \
--hash=sha256:29757bdb7c142f9595cc1b62ec49a3d1c83fab9cef92db52b0ccebaad4eafb98 \
--hash=sha256:4ccba93c1e5a40af45b2f08e4591969fa4697eae951c708f3f83dcbf9f6c6bb1 \
--hash=sha256:4f4a150a737fccae13fb51234d41304ff2222e3b7d4c8e9428ed1a6ab48389b8 \
--hash=sha256:557df28dbccf79b152fe2d1b935f6063d9cc431199ea2b0e84892f35c03bb0ee \
--hash=sha256:5ac5ff9425d8acb8f59ac5b96bcb7fd3d272dc92d96a7c730025928ffcc88a7a \
--hash=sha256:5bae1a073ca4244620425cd3d3aa9746bde590992b98ee8c7c8be8c597ca0d4e \
--hash=sha256:5e56ac47260c0eed992789fa0b8efe43404a9adb608608631a948cee4fc2b052 \
--hash=sha256:6aeadc170090ff1889f0d2c3057557f9cd71f975f17535c26a5d37af98f19c27 \
--hash=sha256:6d5472f63a31b042aadf5ed28dd3ef0523da49ac17f0463e10fda9c4a2773352 \
--hash=sha256:70eda7703b8126f5e52fcf276e6c0f40b0d314674f896fc58c47b0aef2b9ae83 \
--hash=sha256:7df6f6e9d0e33c7b1d435defb185095386c469109de723d514142632a7b9d07f \
--hash=sha256:7e4f9da7e7549946e02a6122dcad00b7c1168513acb1f8a726b1aaf504a99d32 \
--hash=sha256:803f5044b13602d58ea378576dd75aa759f52116a0232608e8fdada4da33752e \
--hash=sha256:808c7190a0fe7ae7014c42f73897cf8e9ef14ff3aa533450e51b1e72ec5239ad \
--hash=sha256:81f6d3b19bc703679a5705c6a16dabdc79823c71d791d73c65949be7f3012c02 \
--hash=sha256:83bbd8354f6abb3fdfb922d1ed47ad8d1db3ea72b0523dac8d07cdacfe1c0fcf \
--hash=sha256:8dd3c2cc49caa7a8d64b67146462aed6723a0495e44bf0aa0a2e94beaa8432f6 \
--hash=sha256:915748cfc25b8cfd81b14d00f4bfdb2ab227a30d6d43459034533f4d1c207a2a \
--hash=sha256:94f3efb718f8f49b031f2071ec7a27dd20cbfe511b4dfd54ecee54c956da2b31 \
--hash=sha256:9bd8fe07f49c170e09d76773fb86ad9135e0beee44f36e1576a201b0676d3d1d \
--hash=sha256:9bf6b699223afe6c7fe9f2ef76e0bfa6dd892c21e94ce8c957478987ade76cd8 \
--hash=sha256:a05ba88adf3d7189a974b2de7a9d56731548d35dc0a822ec3dc669caa7019b29 \
--hash=sha256:a0ac90efbc7a77b0d796c03c8cc4e62fd710b3f1e4c32947713ef2ef52e09543 \
--hash=sha256:a0cb71ccc6ef9ce36eecb6272c81afdc2f565950cdcec33ae8e6cd8f7fc86f27 \
--hash=sha256:a37f40a859b503304dd740686359fcf541d6fb3ff7fc10f539af7f7150917c68 \
--hash=sha256:a911aa73588d9a8b08d662b9484bc0567949529824a55d3885b77e8dd62a127a \
--hash=sha256:aef953f3b8bd0b50bd52a2e52fb54a6a2171a1889d8dea4a5959d46c6624c451 \
--hash=sha256:b28caeaf3e670c08cb7e8de221266df8494c169bd6ed8875493fab45be9607a4 \
--hash=sha256:b30110b29484adc597df6bd92a37b90e63a8c152ca8136aad100a02f8ba6d1b6 \
--hash=sha256:b5b0f7e294700b615a3bcf6d28b26e6da94e8eba63b079f4ec92e9ba6c0d6b54 \
--hash=sha256:c099cafc1834d3c5dac305865d04235f7c21c167c8dd31ebc3d6bbc357e2f023 \
--hash=sha256:d73a0187718f6eec5b2f729b0f98e4603f7bd9c48aa65d01227d1a5dcdfbe9e8 \
--hash=sha256:d8354515ab62f95a07deaf7f845886cc50e2f345ceab240a3d2d09a9f7d77853 \
--hash=sha256:dba72975485f2b87b786075e18a6e5d07dc2b4d8973beb2732b9b2816f1bad70 \
--hash=sha256:dd7546c851e59c06197a7c651335755e74aa383a835878ca86d2c650c07a2f85 \
--hash=sha256:df3ec9959241d07bc261f4983d25a1205ff37703faf42b474f15d54d88b4f8c9 \
--hash=sha256:e1d1735d97fd8a48473af048739379975651fab186f8a25a9f683534e6904179 \
--hash=sha256:e501c096aa3889133d674605ebd018471bc404a59cbc17da3c5924421c54d97c \
--hash=sha256:e7cb9ad1d525d40f7d87b6df7c0ff916a66bc52cb61b66ac1b2a16d0c1b07640 \
--hash=sha256:f4e97a1cf0b7a30af9e1d9dad10a5671157b9acee790d9e26996391f49b965a2 \
--hash=sha256:f8b2acb0ffdd2ce8208accbec2dca4a06937d556fdcaefd6473ba1b5daa7e3c4 \
--hash=sha256:fb04c5650de6668b853623eceadcdb1a9f2fee381f5d7b6bc842ee7c239eeec4 \
--hash=sha256:fbc08c02e9b147a11dfcaa1ac8a83168b699863493e183f7c0c8b12850b7d259 \
--hash=sha256:ff86876889ea478b1381089e55cf9e345707b312beda4986f823e1d95e8c0f59
# via ruamel-yaml
urllib3==2.6.0 \
--hash=sha256:c90f7a39f716c572c4e3e58509581ebd83f9b59cced005b7db7ad2d22b0db99f \
--hash=sha256:cb9bcef5a4b345d5da5d145dc3e30834f58e8018828cbc724d30b4cb7d4d49f1
urllib3==2.5.0 \
--hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \
--hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc
# via requests
xmltodict==1.0.2 \
--hash=sha256:54306780b7c2175a3967cad1db92f218207e5bc1aba697d887807c0fb68b7649 \

View File

@@ -4,8 +4,8 @@
#
Color_Off='\033[0m'
On_Red='\033[41m'
PYTHON_FROM=11
PYTHON_TO=15
PYTHON_FROM=9
PYTHON_TO=14
function detect_docker() {
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}"
fi
# Try to detect a python between lowest and highest supported in reverse order
# Try to detect a python between 3.9 and 3.12 in reverse order
if [ -z "$(which ${SETUP_PYTHON})" ]; then
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
@@ -79,7 +79,7 @@ function detect_python() {
echo "${On_Red}"
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| If you are using a different python version, please set the environment variable SETUP_PYTHON to the correct command - eg. 'python3.11'."
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 "${Color_Off}"
exit 1
fi

View File

@@ -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_ADMIN_PASSWORD_FILE=${CONF_DIR}/admin_password.txt
export SETUP_NO_CALLS=${SETUP_NO_CALLS:-false}
export SETUP_PYTHON=${SETUP_PYTHON:-python3.11}
export SETUP_PYTHON=${SETUP_PYTHON:-python3.9}
export SETUP_ADMIN_NOCREATION=${SETUP_ADMIN_NOCREATION:-false}
# SETUP_DEBUG can be set to get debug info
# SETUP_EXTRA_PIP can be set to install extra pip packages

View File

@@ -4,104 +4,28 @@
This repository hosts the [official documentation](https://inventree.readthedocs.io/) for [InvenTree](https://github.com/inventree/inventree), an open source inventory management system.
## Prerequisites
To serve this documentation locally (e.g. for development), you will need to have Python 3 installed on your system.
InvenTree uses [MkDocs](https://www.mkdocs.org/) to convert [Markdown](https://www.mkdocs.org/user-guide/writing-your-docs/#writing-with-markdown) format `.md` files into HTML suitable for viewing in a web browser.
## Setup
!!! info "Prerequisites"
To build and serve this documentation locally (e.g. for development), you will need:
* Python 3 installed on your system.
* An existing InvenTree installation containing the virtual environment that was created during installation.
These instructions assume you followed the [InvenTree bare metal installation instructions](./docs/start/install.md), so you'll have an `inventree` user, a home directory at `/home/inventree`, the InvenTree source code cloned from [GitHub](https://github.com/inventree/inventree) into `/home/inventree/src`, and a virtual environment at `/home/inventree/env`. If you installed InvenTree some other way, this might vary, and you'll have to adjust these instructions accordingly.
!!! warning "Your InvenTree install will be updated!"
Some of the commands that follow will make changes to your install, for example, by running any pending database migrations. There's a small risk this may cause issues with your existing installation. If you can't risk this, consider setting up a separate InvenTree installation specifically for documentation development.
## Building the documentation locally
To build the documentation locally, run these commands as the `inventree` user:
Run the following commands from the top-level project directory:
```
$ cd /home/inventree
$ source env/bin/activate
```
!!! info "(env) prefix"
The shell prompt should now display the `(env)` prefix, showing that you are operating within the context of the python virtual environment
You can now install the additional packages needed by mkdocs:
```
$ cd src
$ git clone https://github.com/inventree/inventree
$ pip install --require-hashes -r docs/requirements.txt
```
## Build Documentation
## Serve Locally
Before serving the documentation, you will need to build the API schema files from the source code, so they can be included in the documentation:
```
invoke build-docs
```
!!! tip
This command is only required when building the documentation for the first time, or when changes have been made to the API schema.
## Serve Local files
```
$ invoke build-docs
```
You will see output similar to this (truncated for brevity):
```
Running InvenTree database migrations...
Exporting definitions...
Exporting settings definition to '/home/inventree/src/docs/generated/inventree_settings.json'...
Exported InvenTree settings definitions to '/home/inventree/src/docs/generated/inventree_settings.json'
Exported InvenTree tag definitions to '/home/inventree/src/docs/generated/inventree_tags.yml'
Exported InvenTree filter definitions to '/home/inventree/src/docs/generated/inventree_filters.yml'
Exported InvenTree report context definitions to '/home/inventree/src/docs/generated/inventree_report_context.json'
Exporting definitions complete
Exporting schema file to '/home/inventree/src/docs/generated/schema.yml'
Schema export completed: /home/inventree/src/docs/generated/schema.yml
Documentation build complete, but mkdocs not requested
```
If that worked, you can now generate the HTML format documentation pages:
```
$ mkdocs build -f docs/mkdocs.yml
```
## Viewing the generated output
To view the documentation locally, run the following command to start the MkDocs webpage server:
To serve the pages locally, run the following command (from the top-level project directory):
```
$ mkdocs serve -f docs/mkdocs.yml -a localhost:8080
```
Alternatively, you can use the `invoke` command:
## Edit Documentation Files
```
invoke dev.docs-server
```
To see all the available options:
```
invoke dev.docs-server --help
```
You can then point your web browser at http://localhost:8080/
## Editing the Documentation Files
Once the server is running, it will monitor the documentation files for any changes, and regenerate the HTML pages.
Once the server is running, it will monitor the documentation files for any changes, and update the served pages.
### Admonitions

View File

@@ -106,7 +106,7 @@ response = request.get('http://localhost:8080/api/part/', data=data, headers=hea
InvenTree has built-in support for using [oAuth2](https://oauth.net/2/) and OpenID Connect (OIDC) for authentication to the API. This enables using the instance as a very limited identity provider.
A default application using a public client with PKCE enabled ships with each instance. Intended to be used with the python api and configured with very wide scopes this can also be used for quick tests - the client_id is `zDFnsiRheJIOKNx6aCQ0quBxECg1QBHtVFDPloJ6`.
A default application using a public client with PKCE enabled ships with each instance. Intended to be used with the python api and configured with very wide scopes this can also be used for quick tests - the cliend_id is `zDFnsiRheJIOKNx6aCQ0quBxECg1QBHtVFDPloJ6`.
#### Managing applications

View File

@@ -67,7 +67,7 @@ print("Minimum stock:", part.minimum_stock)
### Adding Parameters
Each [part](../../part/index.md) can have multiple [parameters](../../concepts/parameters.md). For the example of the sofa (above) *length* and *weight* make sense. Each parameter has a parameter template that combines the parameter name with a unit. So we first have to create the parameter templates and afterwards add the parameter values to the sofa.
Each [part](../../part/index.md) can have multiple [parameters](../../part/parameter.md). For the example of the sofa (above) *length* and *weight* make sense. Each parameter has a parameter template that combines the parameter name with a unit. So we first have to create the parameter templates and afterwards add the parameter values to the sofa.
```python
from inventree.part import Parameter

View File

@@ -73,7 +73,7 @@ the *Scan Items Into Location* action allows you to scan items into the selected
### Stock Item Actions
From the [Stock Item detail page](./stock.md#details-tab), the following barcode actions may be available:
From the [Stock Item detail page](./stock.md#stock-item-detail-view), the following barcode actions may be available:
{{ image("app/barcode_stock_item_actions.png", "Stock item barcode actions") }}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -69,27 +69,6 @@ To access this page, select *Scan Barcode* from the main navigation menu:
{{ image("barcode/barcode_nav_menu.png", "Barcode menu item") }}
{{ image("barcode/barcode_scan_page.png", "Barcode scan page") }}
## Barcodes in Forms
The InvenTree user interface supports direct scanning of barcodes within certain forms in the web UI. This means that any form field which points to a model which supports barcodes can accept barcode input. If barcode scanning is supported for a particular field, a barcode icon will be displayed next to the input field:
{{ image("barcode/barcode_field.png", "Barcode form field") }}
To scan a barcode into a form field, click this barcode icon. A barcode scanning dialog will be displayed, allowing the user to scan a barcode using their preferred input method:
{{ image("barcode/barcode_field_dialog.png", "Barcode field scan dialog") }}
Once scanned, the form field will be automatically populated with the correct item.
{{ image("barcode/barcode_field_filled.png", "Barcode field populated") }}
Any field which supports barcode input will have this functionality, such as allocating stock items to an order:
{{ image("barcode/barcode_allocate_stock.png", "Allocate stock via barcode") }}
### User Configuration
By default, barcode scanning in form fields is disabled. Each user can enable this feature via their [user preferences](../settings/user.md#display-settings).
## App Integration
@@ -97,15 +76,8 @@ Barcode scanning is a key feature of the [companion mobile app](../app/barcode.m
## Barcode History
If enabled, InvenTree can retain logs of the most recent barcode scans. This can be very useful for debugging or auditing purposes.
If enabled, InvenTree can retain logs of the most recent barcode scans. This can be very useful for debugging or auditing purpopes.
Refer to the [barcode settings](../settings/global.md#barcodes) to enable barcode history logging.
The barcode history can be viewed via the admin panel in the web interface.
## Barcode Settings
There are a number of settings which control the behavior of barcodes within InvenTree. For more information, refer to the links below:
- [Global Barcode Settings](../settings/global.md#barcodes)
- [User Preferences for Barcode Scanning](../settings/user.md#display-settings)

View File

@@ -1,18 +0,0 @@
---
title: Attachments
---
## Attachments
An *attachment* is a file which has been uploaded and linked to a specific object within InvenTree. Attachments can be used to store additional documentation, images, or other relevant files associated with various InvenTree models.
!!! note "Business Logic"
Attachments are not used for any core business logic within InvenTree. They are intended to provide additional metadata for objects, which can be useful for documentation, reference, or reporting purposes.
Parameters can be associated with various InvenTree models.
### Attachments Tab
Any model which supports attachments will have an "Attachments" tab on its detail page. This tab displays all attachments associated with that object:
{{ image("concepts/attachments-tab.png", "Order Attachments Example") }}

View File

@@ -59,9 +59,9 @@ The [unit of measure](../part/index.md#units-of-measure) field for the [Part](..
The [supplier part](../part/index.md/#supplier-parts) model uses real-world units to convert between supplier part quantities and internal stock quantities. Unit conversion rules ensure that only compatible unit types can be supplied
### Parameter
### Part Parameter
The [parameter template](../concepts/parameters.md#parameter-templates) model can specify units of measure, and part parameters can be specified against these templates with compatible units
The [part parameter template](../part/parameter.md#parameter-templates) model can specify units of measure, and part parameters can be specified against these templates with compatible units
## Custom Units

View File

@@ -1,56 +0,0 @@
---
title: InvenTree Development
---
## Introduction
If you are interested in contributing to InvenTree, then this section is for you! Here you will find information about the architecture of InvenTree, how to set up a development environment, and guidelines for contributing code or documentation.
### Architecture Overview
Read the [architecture overview](./architecture.md) to understand the high-level architecture of InvenTree, including how requests are processed, the backend and frontend architecture, and various components of the system.
### Contribution Guide
Start with the [contribution guide](./contributing.md) to understand how to get involved with the InvenTree project.
### Devcontainer Setup
We provide a [devcontainer](./devcontainer.md) configuration to help you quickly set up a development environment using vscode.
### Frontend Development
For information on developing the InvenTree frontend, refer to the [frontend development guide](./react-frontend.md).
## Profiling Tools
The InvenTree project supports integrated profiling tools to help developers analyze and optimize performance. Note that the following tools are intended for development use only and should not be enabled in production environments. In fact, they are explicitly disabled unless the server is running in [debug mode](../start/index.md#debug-mode).
### Django Silk
[django-silk](https://silk.readthedocs.io/en/latest/) is a profiling tool that can be used to monitor and analyze the performance of Django applications. It provides insights into SQL queries, request/response times, and more.
To enable django-silk profiling, ensure that the `debug_silk` option is set to `True` in your [config file](../start/config.md#configuration-file). Alternative, you can set the `INVENTREE_DEBUG_SILK` environment variable to enable this feature.
Once enabled, you can access the silk interface at the `/silk/` endpoint of your InvenTree instance.
!!! tip "Run Migrations"
If you are enabling django-silk for the first time, you may need to run database migrations to create the necessary tables. You can do this by running `invoke migrate`.
#### Detailed Profiling
To enable detailed profiling in django-silk, set the `INVENTREE_DEBUG_SILK_PROFILING` environment variable to `True`, or set the `debug_silk_profiling` option to `True` in your config file. This will enable more granular profiling features within django-silk. Refer to the [django-silk documentation](https://github.com/jazzband/django-silk#profiling) for more information.
### Django QueryCount
Enabling the `INVENTREE_DEBUG_QUERYCOUNT` setting will log (to the terminal) the number of database queries executed for each page load. This can be useful for identifying performance bottlenecks in the InvenTree server. Note that this setting is only available if `INVENTREE_DEBUG` is also enabled.
### Database Logging
Enabling the `INVENTREE_DB_LOGGING` setting will log all database queries to the terminal. This can be useful for debugging database-related issues.
### Internal Profiling Tools
In addition to the above third-party tools, InvenTree includes some internal profiling tools that can be enabled in debug mode. These tools can be used to provide additional insights into the performance of various components of the InvenTree server.
These profiling tools can be found in `./src/backend/InvenTree/profiling.py`.

View File

@@ -98,7 +98,7 @@ Sometimes, users may encounter unexpected error messages when updating their Inv
The most common problem here is that the correct sequence of steps has not been followed:
1. Ensure that the InvenTree [web server](./start/processes.md#web-server) and [background worker](./start/processes.md#background-worker) processes are *halted*
1. Ensure that the InvenTree web server and background worker processes are *halted*
1. Update the InvenTree software (e.g. using git or docker, depending on installation method)
1. Run the `invoke update` command
1. Restart the web server and background worker processes
@@ -150,7 +150,7 @@ or
### Background Worker "Not Running"
The [background worker process](./start/processes.md#background-worker) must be started separately to the web-server application.
The background worker process must be started separately to the web-server application.
From the top-level source directory, run the following command from a separate terminal, while the server is already running:

View File

@@ -31,7 +31,7 @@ Before continuing, it is important that the difference between *untracked* and *
#### BOM Considerations
A [Bill of Materials](./bom.md) to generate an assembly may consist of a mixture of *untracked* and *tracked* components. The build order process can facilitate this, as documented in the sections below.
A [Bill of Materials](./bom.md) to generate an assembly may consist of a mixture of *untracked* and *tracked* components. The build order process can facilitate this, as documentated in the sections below.
### Tracked Build Outputs

View File

@@ -1,21 +1,17 @@
---
title: Parameters
title: Part Parameters
---
## Parameters
## Part Parameters
A *parameter* describes a particular "attribute" or "property" of a specific object in InvenTree. Parameters allow for flexible and customizable data to be stored against various InvenTree models.
A part *parameter* describes a particular "attribute" or "property" of a specific part.
!!! note "Business Logic"
Parameters are not used for any core business logic within InvenTree. They are intended to provide additional metadata for objects, which can be useful for documentation, filtering, or reporting purposes.
Part parameters are located in the "Parameters" tab, on each part detail page.
There is no limit for the number of part parameters and they are fully customizable through the use of [parameter templates](#parameter-templates).
Parameters can be associated with various InvenTree models.
Here is an example of parameters for a capacitor:
### Parameter Tab
Any model which supports parameters will have a "Parameters" tab on its detail page. This tab displays all parameters associated with that object:
{{ image("concepts/parameter-tab.png", "Part Parameters Example") }}
{{ image("part/part_parameters_example.png", "Part Parameters Example") }}
## Parameter Templates
@@ -26,16 +22,13 @@ Parameter templates are used to define the different types of parameters which a
| Name | The name of the parameter template (*must be unique*) |
| Description | Optional description for the template |
| Units | Optional units field (*must be a valid [physical unit](#parameter-units)*) |
| Model Type | The InvenTree model to which this parameter template applies (e.g. Part, Company, etc). If this is left blank, the template can be used for any model type. |
| Choices | A comma-separated list of valid choices for parameter values linked to this template. |
| Checkbox | If set, parameters linked to this template can only be assigned values *true* or *false* |
| Selection List | If set, parameters linked to this template can only be assigned values from the linked [selection list](#selection-lists) |
{{ image("concepts/parameter-template.png", "Parameters Template") }}
### Create Template
Parameter templates are created and edited via the [admin interface](../settings/admin.md).
Parameter templates are created and edited via the [settings interface](../settings/global.md).
To create a template:
@@ -61,11 +54,11 @@ Select the parameter `Template` you would like to use for this parameter, fill-o
## Parametric Tables
Parametric tables gather all parameters from all objects of a particular type, to be sorted and filtered.
Parametric tables gather all parameters from all parts inside a particular [part category](./index.md#part-category) to be sorted and filtered.
Tables views which support parametric filtering and sorting will have a "Parametric View" button above the table:
To access a category's parametric table, click on the "Parameters" tab within the category view:
{{ image("concepts/parametric-parts.png", "Parametric Parts Table") }}
{{ image("part/parametric_table_tab.png", "Parametric Table Tab") }}
### Sorting by Parameter Value
@@ -146,7 +139,7 @@ Parameter sorting takes unit conversion into account, meaning that values provid
### Selection Lists
Selection Lists can be used to add a large number of predefined values to a parameter template. This can be useful for parameters which must be selected from a large predefined list of values (e.g. a list of standardized color codes). Choices on templates are limited to 5000 characters, selection lists can be used to overcome this limitation.
Selection Lists can be used to add a large number of predefined values to a parameter template. This can be useful for parameters which must be selected from a large predefined list of values (e.g. a list of standardised colo codes). Choices on templates are limited to 5000 characters, selection lists can be used to overcome this limitation.
It is possible that plugins lock selection lists to ensure a known state.

View File

@@ -4,7 +4,7 @@ title: Trackable Parts
## Stock Tracking
Denoting a part as *Trackable* changes the way that [stock items](../stock/index.md) associated with the particular part are handled in the database. A trackable part also has more restrictions imposed by the database scheme.
Denoting a part as *Trackble* changes the way that [stock items](../stock/index.md) associated with the particular part are handled in the database. A trackable part also has more restrictions imposed by the database scheme.
For many parts in an InvenTree database, simply tracking current stock levels (and locations) is sufficient. However, some parts require more extensive tracking than simple stock level knowledge.

View File

@@ -39,6 +39,12 @@ A Part is defined in the system by the following parameters:
The Part view page organizes part data into sections, displayed as tabs. Each tab has its own function, which is described in this section.
### Parameters
Parts can have multiple defined parameters.
[Read about Part parameters](./parameter.md)
### Variants
If a part is a *Template Part* then the *Variants* tab will be visible.
@@ -119,18 +125,10 @@ Related parts can be added and are shown under a table of the same name in the "
This feature can be enabled or disabled in the global part settings.
### Parameters
Parts can have multiple defined parameters.
[Read about parameters](../concepts/parameters.md).
### Attachments
The *Part Attachments* tab displays file attachments associated with the selected *Part*. Multiple file attachments (such as datasheets) can be uploaded for each *Part*.
[Read about attachments](../concepts/attachments.md).
### Notes
A part may have notes attached, which support markdown formatting.

View File

@@ -21,7 +21,7 @@ The following builtin plugins are available in InvenTree:
| Barcodes | [TME](./barcode_tme.md) | TME barcode support | No |
| Data Export | [BOM Exporter](./bom_exporter.md) | Custom [exporter](../mixins/export.md) for BOM data | Yes |
| Data Export | [InvenTree Exporter](./inventree_exporter.md) | Custom [exporter](../mixins/export.md) for InvenTree data | Yes |
| Data Export | [Parameter Exporter](./parameter_exporter.md) | Custom [exporter](../mixins/export.md) for parameter data | Yes |
| Data Export | [Parameter Exporter](./part_parameter_exporter.md) | Custom [exporter](../mixins/export.md) for part parameter data | Yes |
| Data Export | [Stocktake Exporter](./stocktake_exporter.md) | Custom [exporter](../mixins/export.md) for stocktake data | No |
| Events | [Auto Create Child Builds](./auto_create_builds.md) | Automatically create child build orders for sub-assemblies | No |
| Events | [Auto Issue Orders](./auto_issue.md) | Automatically issue pending orders when target date is reached | No |

View File

@@ -1,27 +0,0 @@
---
title: Parameter Exporter
---
## Parameter Exporter
The **Parameter Exporter** plugin provides custom export functionality for models which support custom [Parameter](../../concepts/parameters.md) data.
It utilizes the [ExporterMixin](../mixins/export.md) mixin to provide a custom export format for part parameter data.
In addition to the standard exported fields, this plugin also exports all associated parameter data for each row of the export.
### Activation
This plugin is a *mandatory* plugin, and is always enabled.
### Plugin Settings
This plugin has no configurable settings.
## Usage
This plugin is used in the same way as the [InvenTree Exporter Plugin](./inventree_exporter.md), but provides a custom export format for part parameter data.
When exporting parameter data, the *Parameter Exporter* plugin is available for selection in the export dialog. When selected, the plugin provides some additional export options to control the data export process.
{{ image("parameter_export_options.png", base="plugin/builtin", title="Parameter Export Options") }}

View File

@@ -0,0 +1,25 @@
---
title: Part Parameter Exporter
---
## Part Parameter Exporter
The **Part Parameter Exporter** plugin provides custom export functionality for [Part Parameter](../../part/parameter.md) data.
It utilizes the [ExporterMixin](../mixins/export.md) mixin to provide a custom export format for part parameter data.
### Activation
This plugin is a *mandatory* plugin, and is always enabled.
### Plugin Settings
This plugin has no configurable settings.
## Usage
This plugin is used in the same way as the [InvenTree Exporter Plugin](./inventree_exporter.md), but provides a custom export format for part parameter data.
When exporting part parameter data, the *Part Parameter Exporter* plugin is available for selection in the export dialog. When selected, the plugin provides some additional export options to control the data export process.
{{ image("parameter_export_options.png", base="plugin/builtin", title="Part Parameter Export Options") }}

View File

@@ -134,11 +134,11 @@ Validation of the Part IPN (Internal Part Number) field is exposed to custom plu
summary: False
members: []
### Parameter Values
### Part Parameter Values
[Parameters](../../concepts/parameters.md) can also have custom validation rules applied, by implementing the `validate_parameter` method. A plugin which implements this method should raise a `ValidationError` with an appropriate message if the parameter value does not match a required convention.
[Part parameters](../../part/parameter.md) can also have custom validation rules applied, by implementing the `validate_part_parameter` method. A plugin which implements this method should raise a `ValidationError` with an appropriate message if the part parameter value does not match a required convention.
::: plugin.base.integration.ValidationMixin.ValidationMixin.validate_parameter
::: plugin.base.integration.ValidationMixin.ValidationMixin.validate_part_parameter
options:
show_bases: False
show_root_heading: False

View File

@@ -6,7 +6,7 @@ The InvenTree project is grateful to receive resources from various vendors free
Individuals and companies can also support via [GitHub sponsors](https://github.com/sponsors/inventree).
## Current Supporters
## Current supporters
- [DigitalOcean](https://inventree.org/digitalocean) - Cloud hosting provider, used to host the InvenTree demo instance
- [Crowdin](https://crowdin.com/) - Translation platform, used to manage the [InvenTree translations](../develop/contributing.md#translations) across backend, frontend and app
- [SonarQube Cloud](https://sonarcloud.io/) - Code quality and security analysis, used to track the code quality of the various components
@@ -15,8 +15,7 @@ Individuals and companies can also support via [GitHub sponsors](https://github.
- [Netlify](https://www.netlify.com/) - Static site hosting provider, used to test deploy the frontend and website
- [Depot](https://depot.dev/?utm_source=inventree) - Docker build accelerator, used to build the multi-arch images for the InvenTree docker image
## Past Supporters
Non comprehensive list of past supporters. The project stops consuming resources for various reasons, this does not mean they are not good resources.
## Past supporters
Non cromprehensive list of past supporters. The project stops consuming resources for various reasons, this does not mean they are not good resources.
- [Coveralls](https://coveralls.io/) - Code coverage as a service
- [Deepsource](https://deepsource.io/) - Code quality and security analysis

View File

@@ -545,11 +545,11 @@ You can add asset images to the reports and labels by using the `{% raw %}{% ass
{% endraw %}
```
## Parameters
## Part Parameters
If you need to load a parameter value for a particular model instance, within the context of your template, you can use the `parameter` template tag:
If you need to load a part parameter for a particular Part, within the context of your template, you can use the `part_parameter` template tag:
::: report.templatetags.report.parameter
::: report.templatetags.report.part_parameter
options:
show_docstring_description: false
show_source: False
@@ -562,7 +562,7 @@ The following example assumes that you have a report or label which contains a v
{% raw %}
{% load report %}
{% parameter part "length" as length %}
{% part_parameter part "length" as length %}
Part: {{ part.name }}<br>
Length: {{ length.data }} [{{ length.units }}]
@@ -570,7 +570,7 @@ Length: {{ length.data }} [{{ length.units }}]
{% endraw %}
```
A [Parameter](../concepts/parameters.md) has the following available attributes:
A [Part Parameter](../part/parameter.md) has the following available attributes:
| Attribute | Description |
| --- | --- |
@@ -578,7 +578,7 @@ A [Parameter](../concepts/parameters.md) has the following available attributes:
| Description | The *description* of the parameter |
| Data | The *value* of the parameter (e.g. "123.4") |
| Units | The *units* of the parameter (e.g. "km") |
| Template | A reference to a [ParameterTemplate](../concepts/parameters.md#parameter-templates) |
| Template | A reference to a [PartParameterTemplate](../part/parameter.md#parameter-templates) |
## Rendering Markdown

View File

@@ -44,45 +44,5 @@ For example, rendering the name of a part (which is available in the particular
</p></i>
{% endraw %}
```
## Merging Reports
When rendering reports for multiple items, the default behaviour is that each item is rendered as a separate report. The chosen templeate is rendered multiple times, once for each item selected, and expects a single item in the context variable.
However, it is possible to merge multiple items into a single report document. This is achieved by enabling the `merge` attribute of the report template:
{{ image("report/report_merge.png", alt="Report Merge Option") }}
When the `merge` is enabled, all selected items are passed to the report template in the `instances` context variable, which is a list of all selected items. The user can then iterate over this list to render multiple items in a single report document.
### Instance Context
When rendering a single template against multiple *instances* of a particular model (e.g. multiple parts, multiple stock items, etc), each instance being rendered has its own unique context data.
Each "instance" is provided to the report template as a dictionary of context variables, which can be accessed using standard django template syntax.
Refer to the [context variable documentation](./context_variables.md) for more information about the available context variables for each model type.
The `instances` variable is provided as a list of all selected items, where each item in the list is a dictionary of context variables for that particular instance. Within the report template, the user can iterate over the `instances` list to render each item in turn.
### Example
As an example, let's consider a report template where we are printing multiple parts in a single report document.
When the `merge` option is enabled, the report template is provided with an `instances` variable, which is a list of all selected parts.
Each *instance* in the `instances` list is a dictionary of context variables for that particular part, which conforms to the standard [part context structure](./context_variables.md#part).
```django
{% raw %}
{% for instance in instances %}
Part Name: {{ instance.part.name }} <br>
IPN: {{ instance.part.IPN }} <br>
Description: {{ instance.part.description }} <br>
{% endraw %}
```
!!! tip "Instance Prefix"
Note that the context variable is prefixed with `instance.` when accessing variables for each item in the `instances` list.
#### Rendering a single report vs. multiple report from selection
Users can select multiple items such as `part`, `stockItem`,...etc to render from a report template. By default, the `merge` attribute of report template is disabled, which means an independent report will be generated for each item in the list of selected items. If `merge` is enabled, all selected items will be available in the `instances` context variable of the report template. Users are free to access them by indexing or in a loop. For more details, visit [context variable](./context_variables.md)

View File

@@ -13,9 +13,6 @@ We use the powerful [WeasyPrint](https://weasyprint.org/) PDF generation engine
Templates are rendered using standard HTML / CSS - if you are familiar with web page layout, you're ready to go!
HTML form elements (`input`, `select`, `textarea`, `button`) are converted into PDF form controls. Use CSS to limit which elements are converted; refer to the
[WeasyPrint docs](https://doc.courtbouillon.org/weasyprint/stable/common_use_cases.html#include-pdf-forms) for further information.
### Template Language
Uploaded report template files are passed through the [django template rendering framework]({% include "django.html" %}/topics/templates/), and as such accept the same variable template strings as any other django template file. Different variables are passed to the report template (based on the context of the report) and can be used to customize the contents of the generated PDF.

View File

@@ -4,7 +4,7 @@ title: InvenTree Multi Factor Authentication
## Multi Factor Authentication
InvenTree gives the option to use [TOTP](https://en.wikipedia.org/wiki/Time-based_One-Time_Password) or statically generated backup tokens as an additional factor to password or SSO authentication. This is a widely adopted security feature on enterprise web services. We highly encourage to enable it if you expose your instance to the public internet.
InvenTree gives the option to use TOTP or statically generated backup tokens as an additional factor to password or SSO authentication. This is a widely adopted security feature on enterprise web services. We highly encourage to enable it if you expose your instance to the public internet.
As TOTP is an [open standard](https://datatracker.ietf.org/doc/html/rfc6238) there are a lot of different ways to hold your key and generate the time based tokens needed for authentication. That ranges from physical devices to password managers and mobile apps. We do not advertise any method but recommend to keep password and token generator separate from each other.
@@ -16,4 +16,4 @@ To make MFA mandatory for all users:
### Security Consideration
A user can lock themselves out if they lose access to both the device with their TOTP app and their backup tokens. An admin can delete their tokens from the admin pages (they exist under the 'TOTP devices' / 'static devices' models) . This should be a last resort and only done by people knowledgeable about the [admin pages](../settings/admin.md) as changes there might circumvent InvenTree's business and security logic.
A user can lock themself out if they lose access to both the device with their TOTP app and their backup tokens. An admin can delete their tokens from the admin pages (they exist under the 'TOTP devices' / 'static devices' models) . This should be a last resort and only done by people knowledgeable about the [admin pages](../settings/admin.md) as changes there might circumvent InvneTrees business and security logic.

View File

@@ -9,7 +9,7 @@ InvenTree can be configured to send emails to users, for various purposes.
To enable this, email configuration settings must be supplied to the InvenTree [configuration options](../start/config.md#email-settings).
!!! info "Functionality might be degraded"
Multiple functions of InvenTree require functioning email delivery, including *Password Reset*, *Notifications*, *Update Infos*
Multiple functions of InvenTree require functioning email delivery, including *Password Reset*, *Notififications*, *Update Infos*
### Outgoing

View File

@@ -83,24 +83,6 @@ This is a security measure to prevent plugins from changing the core functionali
An error occurred when discovering or initializing a machine type from a plugin. This likely indicates a faulty or incompatible plugin.
#### INVE-E13
**Error reading InvenTree configuration file**
An error occurred while reading the InvenTree configuration file. This might be caused by a syntax error or invalid value in the configuration file.
#### INVE-E14
**Could not import Django**
Django is not installed in the current Python environment. This means that the InvenTree backend is not running within the correct [python virtual environment](../start/index.md#virtual-environment) or that the required Python packages were not installed correctly.
#### INVE-E15
**Python version not supported**
This error occurs attempting to run InvenTree on a version of Python which is older than the minimum required version. We [require Python {{ config.extra.min_python_version }} or newer](../start/index.md#python-requirements)
### INVE-W (InvenTree Warning)
Warnings - These are non-critical errors which should be addressed when possible.
@@ -171,7 +153,7 @@ The warning text will show the recommended command for intended use.
#### INVE-W10
**Config not in recommended directory - Backend**
A configuration file is not in the recommended directory. This might lead to issues with the deployment method you are using. It might also lead to confusion.
A configuration file is not in the recommended directory. This might lead to issues with the deployment method you are using. It might also lead to confusinon.
The warning text will show the recommended directory for your deployment method.

View File

@@ -78,7 +78,7 @@ If this setting is enabled, users can reset their password via email. This requi
If this setting is enabled, users must have multi-factor authentication enabled to log in.
#### Auto Fill SSO Users
#### Auto Fil SSO Users
Automatically fill out user-details from SSO account-data. If this feature is enabled the user is only asked for their username, first- and surname if those values can not be gathered from their SSO profile. This might lead to unwanted usernames bleeding over.
@@ -174,14 +174,11 @@ Configuration of label printing:
{{ globalsetting("PART_COPY_TESTS") }}
{{ globalsetting("PART_CATEGORY_PARAMETERS") }}
{{ globalsetting("PART_CATEGORY_DEFAULT_ICON") }}
{{ globalsetting("PART_PARAMETER_ENFORCE_UNITS") }}
#### Parameter Templates
#### Part Parameter Templates
| Name | Description | Default | Units |
| ---- | ----------- | ------- | ----- |
{{ globalsetting("PARAMETER_ENFORCE_UNITS") }}
For more information on parameters, refer to the [parameter documentation](../concepts/parameters.md).
Refer to the section describing [how to create part parameter templates](../part/parameter.md#create-template).
### Categories

View File

@@ -22,7 +22,6 @@ The *Display Settings* screen shows general display configuration options:
{{ usersetting("STICKY_HEADER") }}
{{ usersetting("STICKY_TABLE_HEADER") }}
{{ usersetting("SHOW_SPOTLIGHT") }}
{{ usersetting("BARCODE_IN_FORM_FIELDS") }}
{{ usersetting("DATE_DISPLAY_FORMAT") }}
{{ usersetting("FORMS_CLOSE_USING_ESCAPE") }}
{{ usersetting("DISPLAY_STOCKTAKE_TAB") }}

View File

@@ -143,7 +143,7 @@ pip install django-storages[google]
You will need to change the storage backend, which is set via the `INVENTREE_BACKUP_STORAGE` environment variable, or via `backup_storage` in the configuration file:
```yaml
backup_storage: storages.backends.gcloud.GoogleCloudStorage
backup_stoage: storages.backends.gcloud.GoogleCloudStorage
```
### Configure Backend Options

View File

@@ -38,7 +38,7 @@ First, let's confirm that the gunicorn server is operational.
cd /home/InvenTree
source ./env/bin/activate
cd src/src/backend/InvenTree
cd src/InvenTree
/home/inventree/env/bin/gunicorn -c gunicorn.conf.py InvenTree.wsgi -b 127.0.0.1:8000
```

View File

@@ -92,9 +92,7 @@ The following debugging / logging options are available:
| Environment Variable | Configuration File | Description | Default |
| --- | --- | --- | --- |
| INVENTREE_DEBUG | debug | Enable [debug mode](./index.md#debug-mode) | False |
| INVENTREE_DEBUG_QUERYCOUNT | debug_querycount | Enable support for [django-querycount](../develop/index.md#django-querycount) middleware. | False |
| INVENTREE_DEBUG_SILK | debug_silk | Enable support for [django-silk](../develop/index.md#django-silk) profiling tool. | False |
| INVENTREE_DEBUG_SILK_PROFILING | debug_silk_profiling | Enable detailed profiling in django-silk | False |
| INVENTREE_DEBUG_QUERYCOUNT | debug_querycount | Enable [query count logging](https://github.com/bradmontgomery/django-querycount) in the terminal | False |
| INVENTREE_DB_LOGGING | db_logging | Enable logging of database messages | False |
| INVENTREE_LOG_LEVEL | log_level | Set level of logging to terminal | WARNING |
| INVENTREE_JSON_LOG | json_log | log as json | False |
@@ -105,7 +103,13 @@ The following debugging / logging options are available:
Enabling the `INVENTREE_DEBUG` setting will turn on [Django debug mode]({% include "django.html" %}/ref/settings/#debug). This mode is intended for development purposes, and should not be enabled in a production environment. Read more about [InvenTree debug mode](./index.md#debug-mode).
In debug mode, there are additional [profiling tools](../develop/index.md#profiling-tools) available to help developers analyze and optimize performance.
### Query Count Logging
Enabling the `INVENTREE_DEBUG_QUERYCOUNT` setting will log the number of database queries executed for each page load. This can be useful for identifying performance bottlenecks in the InvenTree server. Note that this setting is only available if `INVENTREE_DEBUG` is also enabled.
### Database Logging
Enabling the `INVENTREE_DB_LOGGING` setting will log all database queries to the terminal. This can be useful for debugging database-related issues.
## Server Access
@@ -262,9 +266,6 @@ The following database options can be configured:
| INVENTREE_DB_HOST | database.HOST | Database host address (if required) | *Not specified* |
| INVENTREE_DB_PORT | database.PORT | Database host port (if required) | *Not specified* |
!!! tip "Database Password"
The value specified for `INVENTREE_DB_PASSWORD` should not contain comma `,` or colon `:` characters, otherwise the connection to the database may fail.
### PostgreSQL Settings
If running with a PostgreSQL database backend, the following additional options are available:
@@ -317,9 +318,6 @@ The following cache settings are available:
| INVENTREE_CACHE_KEEPALIVE_INTERVAL | cache.keepalive_interval | Cache keepalive interval | 1 |
| INVENTREE_CACHE_USER_TIMEOUT | cache.user_timeout | Cache user timeout | 1000 |
!!! tip "Cache Password"
The value specified for `INVENTREE_CACHE_PASSWORD` should not contain comma `,` or colon `:` characters, otherwise the connection to the cache server may fail.
## Email Settings
To enable [email functionality](../settings/email.md), email settings must be configured here, either via environment variables or within the configuration file.
@@ -338,10 +336,6 @@ The following email settings are available:
| INVENTREE_EMAIL_SENDER | email.sender | Sending email address | *Not specified* |
| INVENTREE_EMAIL_PREFIX | email.prefix | Prefix for subject text | [InvenTree] |
### Email Backend
The default email implementation uses the Django STMP backend. This should be sufficient for most implementations, although other backends can be used if required. Note that selection of a different backend requires must use fully qualified module path, and requires advanced knowledge.
### Sender Email
The "sender" email address is the address from which InvenTree emails are sent (by default) and must be specified for outgoing emails to function:
@@ -431,7 +425,7 @@ InvenTree provides allowance for additional sign-in options. The following optio
| Environment Variable | Configuration File | Description | Default |
| --- | --- | --- | --- |
| INVENTREE_MFA_ENABLED | mfa_enabled | Enable or disable multi-factor authentication support for the InvenTree server | True |
| INVENTREE_MFA_SUPPORTED_TYPES | mfa_supported_types | List of supported multi-factor authentication types | recovery_codes,totp,webauthn |
| INVENTREE_MFA_SUPPORTED_TYPES | mfa_supported_types | List of supported multi-factor authentication types | recovery_codes,totp |
### Single Sign On
@@ -488,7 +482,7 @@ The INVENTREE_CUSTOMIZE environment variable must contain a json object with the
the wanted values. Example:
```
INVENTREE_CUSTOMIZE={"login_message":"Hello World"}
INVENTREE_CUSTOMIZE={"login_message":"Hallo Michi"}
```
This example sets a login message. Take care of the double quotes.

View File

@@ -235,21 +235,6 @@ The [Caddy](./docker.md#ssl-certificates) container will automatically generate
Any persistent files generated by the Caddy container (such as certificates, etc) will be stored in the `caddy` directory within the external volume.
### Web Server Bind Address
By default, the Dockerized InvenTree web server binds to all available network interfaces and listens for IPv4 traffic on port 8000.
This can be adjusted using the following environment variables:
| Environment Variable | Default |
| --- | --- | --- | --- |
| INVENTREE_WEB_ADDR | 0.0.0.0 |
| INVENTREE_WEB_PORT | 8000 |
These variables are combined in the [Dockerfile]({{ sourcefile("contrib/container/Dockerfile") }}) to build the bind string passed to the InvenTree server on startup.
!!! tip "IPv6 Support"
To enable IPv6/Dual Stack support, set `INVENTREE_WEB_ADDR` to `[::]` when you create/start the container.
### Demo Dataset
To quickly get started with a [demo dataset](../demo.md), you can run the following command:

View File

@@ -123,14 +123,11 @@ By default, a production InvenTree installation is configured to run with [DEBUG
Running in DEBUG mode provides many handy development features, however it is strongly recommended *NOT* to run in DEBUG mode in a production environment. This recommendation is made because DEBUG mode leaks a lot of information about your installation and may pose a security risk.
So, for a production setup, you should ensure that `INVENTREE_DEBUG=false` in the [configuration options](./config.md).
!!! warning "Security Risk"
Running InvenTree in DEBUG mode in a production environment is a significant security risk, and should be avoided at all costs.
So, for a production setup, you should set `INVENTREE_DEBUG=false` in the [configuration options](./config.md).
### Turning Debug Mode off
When running in DEBUG mode, the InvenTree web server natively manages *static* and *media* files, which means that when DEBUG mode is *disabled* (which is the default for a production setup), the proxy setup has to be configured to handle this.
When running in DEBUG mode, the InvenTree web server natively manages *static* and *media* files, which means that when DEBUG mode is *disabled*, the proxy setup has to be configured to handle this.
!!! info "Read More"
Refer to the [proxy server documentation](./processes.md#proxy-server) for more details

View File

@@ -108,27 +108,6 @@ The Python packages required by the InvenTree server must be installed into the
```
pip install --upgrade --ignore-installed invoke
```
#### Install Python Bindings
Depending on your database the python bindings must also be installed (into your virtual environment).
For PostgreSQL install:
```
pip3 install psycopg pgcli
```
For MySQL install:
```
pip3 install mysqlclient mariadb
```
If all packages have been installed run:
```
invoke install
```
@@ -176,6 +155,14 @@ grant all privileges on database inventree to myuser;
!!! info "Username / Password"
You should change the username and password from the values specified above. This username and password will also be for the InvenTree database connection configuration.
#### Install Python Bindings
The PostgreSQL python binding must also be installed (into your virtual environment):
```
pip3 install psycopg pgcli
```
#### Install Postgresql client
If PostgreSQL and InvenTree are installed on separate servers / containers the PostgreSQL client has to be installed also where InvenTree is running.
@@ -197,6 +184,14 @@ To run InvenTree with the MySQL or MariaDB backends, a number of extra packages
sudo apt-get install mysql-server libmysqlclient-dev
```
#### Install Python Bindings
Install the python bindings for MySQL (into the python virtual environment).
```
pip3 install mysqlclient mariadb
```
#### Create Database
Assuming the MySQL server is installed and running, login to the MySQL server as follows:

View File

@@ -114,86 +114,3 @@ Once the migration process completes, the database records are now updated! Rest
!!! tip "Example: Docker"
If running under docker, run `docker compose up -d`
## Migrating Between Incompatible Database Versions
There may be occasions when InvenTree data needs to be migrated between two database installations running *incompatible* versions of the database software. For example, InvenTree may be running on a Postgres database running on version 12, and the administrator wishes to migrate the data to a Postgres version 17 database.
!!! warning "Advanced Procedure"
The following procedure is *advanced*, and should only be attempted by experienced database administrators. Always ensure that database backups are made before attempting any migration procedure.
Due to inherit incompatibilities between different major versions of database software, it is not always possible to directly migrate data between two different database versions. In such cases, the following procedure can be used as a workaround.
!!! warning "InvenTree Version"
It is *crucial* that both InvenTree database installations are running the same version of InvenTree software! If this is not the case, data migration may fail, and there is a possibility that data corruption can occur. Ensure that the original database is up to date, by running `invoke update`.
The following instructions assume that the source (old) database is Postgres version 15, and the target (new) database is Postgres version 17. Additionally, it assumes that the InvenTree installation is running under [docker / docker compose](./docker.md), for simplicity. Adjust commands as required for other InvenTree configurations or database software.
The overall procedure is as follows:
### Backup Old Database
Run the following command to create a backup dump of the old database:
```
docker compose run --rm inventree-server invoke backup
```
This will create a database backup file in the InvenTree backup directory.
!!! tip "Secondary Backup"
It may be prudent to create a secondary backup of the database, separate to the one created by InvenTree.
### Shutdown Old Database
Stop the old InvenTree installation, to ensure that the database is not being accessed during the migration process:
```
docker compose down
```
### Remove Old Database Files
The raw database files are incompatible between different major versions of Postgres. Thus, the old database files must be removed before starting the new database.
!!! warning "Data Loss"
Ensure that a complete backup of the old database has been made before proceeding! Removing the database files will result in data loss if a backup does not exist.
```
rm -rf ./path/to/database/*
```
!!! info "Database Location"
The location of the database files depends on how InvenTree was configured.
### Start New Database
Update the InvenTree docker configuration to use the new version of Postgres (e.g. `postgres:17`), and then start the InvenTree installation:
```
docker compose up -d
```
This will initialize a new, empty database using the new version of Postgres.
### Run Database Migrations
Run the database migration process to ensure that the new database schema is correctly initialized:
```
docker compose run --rm inventree-server invoke update
```
### Restore Database Backup
Finally, restore the database backup created earlier into the new database:
```
docker compose run --rm inventree-server invoke restore
```
This will load the database records from the backup file into the new database.
### Caveats
The process described here is a *suggested* procedure for migrating between incompatible database versions. However, due to the complexity of database software, there may be unforeseen complications that arise during the process.

View File

@@ -1,88 +0,0 @@
---
title: Stock Availability
---
## Stock Availability
Each stock item represents a physical quantity of a particular part in a specific location. Given the complexities of tracking stock across multiple locations, reservations, and allocations, it is important to understand whether a stock item is actually available for use.
There are multiple terms used in InvenTree to describe stock availability:
### Required
The *Required* quantity indicates the total number of a certain [part](../part/index.md) that is needed to fulfill all current orders, builds, or other commitments. This is independent of the actual stock levels.
### In Stock
The *In Stock* quantity represents the total number of items physically present in the stock location, regardless of their allocation or reservation status.
For example, if a stock item has a quantity of 100, it means there are 100 units of that part physically present in the specified location.
### Allocated
The *Allocated* quantity indicates the number of stock items that have been reserved for specific orders, builds, or other commitments. This number is specified against each stock item when it is allocated.
Note that the *Allocated* quantity is always less than or equal to the *In Stock* quantity.
For example, if a stock item has an *In Stock* quantity of 100, and 30 units have been allocated to fulfill orders, then the *Allocated* quantity is 30. In this case the *In Stock* quantity remains 100, until the alloacted quantity is actually consumed.
### Available
The *Available* quantity for a given stock item is the difference between the *In Stock* quantity and the *Allocated* quantity.
For example, if a stock item has an *In Stock* quantity of 100, and 30 units have been allocated, then the *Available* quantity is 70.
## Consumed or Depleted Stock
Once allocated stock quantities are consumed (e.g. when fulfilling an order or completing a build), the *In Stock* quantity of the stock item is reduced accordingly. This means that the *Available* quantity is also reduced.
### Delete on Deplete
Stock items can be designated with the `Delete on Deplete` flag. When this flag is set, if the stock quantity of the item reaches zero, the stock item will be automatically deleted from the system.
## Part Stock Levels
There are multiple ways to view the overall stock levels for a given part across all stock items and locations.
### Stock Overview
Each [part](../part/index.md) page displays an overview of the stocktotal stock levels across all locations. This page shows the total *In Stock*, *Allocated*, and *Available* quantities for the part, aggregated across all stock items:
{{ image("stock/stock_overview.png", title="Stock overview") }}
In the example above, the "Red Widget" part has the following stock levels:
| Metric | Quantity | Description |
| ------ | -------- | ----------- |
| In Stock | 138 | There are 138 physical units of the Red Widget part across all stock locations. |
| Available | 123 | Only 123 units are available for allocation, as 15 units have already been allocated to fulfill orders. |
| Required | 657 | A total of 657 units of the Red Widget part are needed to fulfill all current orders and builds. |
| Deficit | 519 | There is a deficit of 519 units, meaning that the current available stock is insufficient to meet the required quantity. |
### Stock Details
The *Part Detail* view displays additional information regarding the stock levels for the part:
{{ image("stock/stock_detail.png", title="Stock detail") }}
Here we can see that:
- There are 138 units of the Red Widget part physically in stock across all locations.
- Of this quantity, 15 units have been allocated to fulfill orders, leaving 123 units available for allocation.
- A total of 657 units are required to fulfill all current orders and builds.
- 645 units are required to fulfil outstanding build orders, of which 15 units have already been allocated.
- 12 units are required to fulfil outstanding sales orders, none of which have been allocated yet.
### Allocations Tab
To view further details regarding which stock items have been allocated, the *Allocations* tab on the part view can be used.
This tab displays a complete overview of where the stock items for this part are allocated, against any open orders:
#### Build Order Allocations
{{ image("stock/build_order_allocations.png", title="Build order allocations") }}
#### Sales Order Allocations
{{ image("stock/sales_order_allocations.png", title="Sales order allocations") }}

View File

@@ -26,15 +26,11 @@ The *Stock Item* detail view shows information regarding the particular stock it
**Status** - Status of this stock item
### Stock Availability
InvenTree has a number of different mechanisms to determine whether stock is available for use. See the [Stock Availability](./availability.md) page for more information.
### Stock Tracking
Every time a *Stock Item* is adjusted, a *Stock Tracking* entry is automatically created. This ensures a complete history of the *Stock Item* is maintained as long as the item is in the system.
Each stock tracking historical item records the user who performed the action. [Read more about stock tracking here](./tracking.md).
Each stock tracking historical item records the user who performed the action.
## Stock Location
@@ -53,10 +49,12 @@ If there are some icons missing in the tabler icons package, users can even inst
A stock location type represents a specific type of location (e.g. one specific size of drawer, shelf, ... or box) which can be assigned to multiple stock locations. In the first place, it is used to specify an icon and having the icon in sync for all locations that use this location type, but it also serves as a data field to quickly see what type of location this is. It is planned to add e.g. drawer dimension information to the location type to add a "find a matching, empty stock location" tool.
## External Stock Location
It may be useful to mark certain stock locations as *external*. An external stock location can be used to indicate that items in there might not be available for immediate usage. Stock items in an external location are marked with an additional icon
An external stock location can be used to indicate that items in there might not be available
for immediate usage. Stock items in an external location are marked with an additional icon
in the build order line items view where the material is allocated.
{{ image("stock/stock_external_icon.png", title="External stock indication") }}
Anyhow there is no limitation on the stock item. It can be allocated as usual.
The external flag does not get inherited to sublocations.

View File

@@ -132,17 +132,6 @@ If a provided serial number (or group of numbers) is not considered valid, an er
!!! tip "Serial Number Validation"
Custom serial number validation can be implemented using an external plugin
#### Adjusting Serial Numbers
Once a stock item has been created with a serial number, it is possible to adjust that serial number value if required. This can be achieved by navigating to the stock item's detail page, and selecting the "Edit" option from the actions menu. Here, the serial number value can be modified as required:
{{ image("stock/serial_edit.png", title="Editing a serial number") }}
Note that any serial number adjustments are subject to the same validation rules as when the stock item was created. If the new serial number value is not valid, an error message will be displayed to the user:
{{ image("stock/serial_edit_error.png", title="Error while editing a serial number") }}
#### Plugin Support
Custom serial number functionality, with any arbitrary requirements or level of complexity, can be implemented using the [Validation Plugin Mixin class](../plugins/mixins/validation.md#serial-numbers). Refer to the documentation for this plugin for technical details.

View File

@@ -9,12 +9,6 @@ If you are struggling with an issue which is not covered in the FAQ above, pleas
Even if you cannot immediately resolve the issue, the information below will be very useful when reporting the issue on GitHub.
## Error Codes
InvenTree uses a system of error codes to help identify specific issues. Each error code is prefixed with `INVE-`, followed by a letter indicating the error type, and a number.
Refer to the [error code documentation](./settings/error_codes.md) for more information on specific error codes.
## Recent Update
If you have recently updated your InvenTree instance, please ensure that you have followed all update instructions carefully. In particular, make sure that you have run any required database migrations using the `invoke update` command.

View File

@@ -78,7 +78,6 @@ nav:
- Demo: demo.md
- Release Notes: releases/release_notes.md
- Development:
- InvenTree Development: develop/index.md
- Contributing: develop/contributing.md
- Architecture: develop/architecture.md
- Roadmap: develop/roadmap.md
@@ -97,8 +96,6 @@ nav:
- Custom States: concepts/custom_states.md
- Pricing: concepts/pricing.md
- Project Codes: concepts/project_codes.md
- Attachments: concepts/attachments.md
- Parameters: concepts/parameters.md
- Barcodes:
- Barcode Support: barcodes/index.md
- Internal Barcodes: barcodes/internal.md
@@ -128,6 +125,7 @@ nav:
- Virtual Parts: part/virtual.md
- Part Views: part/views.md
- Tracking: part/trackable.md
- Parameters: part/parameter.md
- Revisions: part/revision.md
- Templates: part/template.md
- Tests: part/test.md
@@ -136,9 +134,8 @@ nav:
- Notifications: part/notification.md
- Stock:
- Stock Items: stock/index.md
- Availability: stock/availability.md
- Stock Tracking: stock/tracking.md
- Stock Status: stock/status.md
- Stock Tracking: stock/tracking.md
- Adjusting Stock: stock/adjust.md
- Stock Expiry: stock/expiry.md
- Stock Ownership: stock/owner.md
@@ -240,7 +237,7 @@ nav:
- Export Plugins:
- BOM Exporter: plugins/builtin/bom_exporter.md
- InvenTree Exporter: plugins/builtin/inventree_exporter.md
- Parameter Exporter: plugins/builtin/parameter_exporter.md
- Parameter Exporter: plugins/builtin/part_parameter_exporter.md
- Stocktake Exporter: plugins/builtin/stocktake_exporter.md
- Label Printing:
- Label Printer: plugins/builtin/inventree_label.md
@@ -360,10 +357,10 @@ extra:
# provider: google
# property: UA-143467500-1
min_python_version: 3.11
min_python_version: 3.9
min_invoke_version: 2.0.0
django_version: 5.2
docker_postgres_version: 17
django_version: 4.2
docker_postgres_version: 16
version:
default: stable

View File

@@ -6,5 +6,5 @@ mkdocs-redirects
mkdocs-simple-hooks>=0.1,<1.0
mkdocs-include-markdown-plugin
neoteroi-mkdocs
mkdocstrings[python]>=0.25.0,<=1.0.0
mkdocstrings[python]>=0.25.0,<=0.30.1
mkdocs-mermaid2-plugin

View File

@@ -148,10 +148,14 @@ essentials==1.1.6 \
--hash=sha256:3fd26923f5f2ece51a219dbb17b1fb22c9190d70fa2104919be92a6419521877 \
--hash=sha256:a470e693d83c13369ebf1f488d60236b4ea99400f38db6b7224e2808c1369256
# via essentials-openapi
essentials-openapi==1.3.0 \
--hash=sha256:453327a0a847a431133f4472ced7e4a9180bf667437049b57381ddf88079e886 \
--hash=sha256:9c2a88531e2c70c565d5b526d74043941e46f60c114f7a0e3ae91e9e6bef4dae
essentials-openapi==1.2.1 \
--hash=sha256:70b06d80a8d9cb7634b702903cfe8fcfc48e6fc054e744eab514bb7bc37f00c9 \
--hash=sha256:d0085cde3ceaa25e6fc4983d8372ac0185909e3fa2791f498280379bb3c86c62
# via neoteroi-mkdocs
exceptiongroup==1.3.0 \
--hash=sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10 \
--hash=sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88
# via anyio
ghp-import==2.1.0 \
--hash=sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619 \
--hash=sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343
@@ -193,6 +197,14 @@ idna==3.10 \
# anyio
# httpx
# requests
importlib-metadata==8.7.0 \
--hash=sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000 \
--hash=sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd
# via
# markdown
# mkdocs
# mkdocs-get-deps
# mkdocstrings
jinja2==3.1.6 \
--hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
--hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
@@ -330,13 +342,13 @@ mkdocs-include-markdown-plugin==7.2.0 \
--hash=sha256:4a67a91ade680dc0e15f608e5b6343bec03372ffa112c40a4254c1bfb10f42f3 \
--hash=sha256:d56cdaeb2d113fb66ed0fe4fb7af1da889926b0b9872032be24e19bbb09c9f5b
# via -r docs/requirements.in
mkdocs-macros-plugin==1.5.0 \
--hash=sha256:12aa45ce7ecb7a445c66b9f649f3dd05e9b92e8af6bc65e4acd91d26f878c01f \
--hash=sha256:c10fabd812bf50f9170609d0ed518e54f1f0e12c334ac29141723a83c881dd6f
mkdocs-macros-plugin==1.4.1 \
--hash=sha256:55a9c93871e3744cdeb0736316783d60830a6d5d97b1132364e6b491607f2332 \
--hash=sha256:5a9e483f6056fe7ad0923802affe699233ca468672e20a9640dba237165b3240
# via -r docs/requirements.in
mkdocs-material==9.7.0 \
--hash=sha256:602b359844e906ee402b7ed9640340cf8a474420d02d8891451733b6b02314ec \
--hash=sha256:da2866ea53601125ff5baa8aa06404c6e07af3c5ce3d5de95e3b52b80b442887
mkdocs-material==9.6.22 \
--hash=sha256:14ac5f72d38898b2f98ac75a5531aaca9366eaa427b0f49fc2ecf04d99b7ad84 \
--hash=sha256:87c158b0642e1ada6da0cbd798a3389b0bc5516b90e5ece4a0fb939f00bacd1c
# via -r docs/requirements.in
mkdocs-material-extensions==1.3.1 \
--hash=sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443 \
@@ -354,9 +366,9 @@ mkdocs-simple-hooks==0.1.5 \
--hash=sha256:dddbdf151a18723c9302a133e5cf79538be8eb9d274e8e07d2ac3ac34890837c \
--hash=sha256:efeabdbb98b0850a909adee285f3404535117159d5cb3a34f541d6eaa644d50a
# via -r docs/requirements.in
mkdocstrings[python]==1.0.0 \
--hash=sha256:351a006dbb27aefce241ade110d3cd040c1145b7a3eb5fd5ac23f03ed67f401a \
--hash=sha256:4c50eb960bff6e05dfc631f6bc00dfabffbcb29c5ff25f676d64daae05ed82fa
mkdocstrings[python]==0.30.1 \
--hash=sha256:41bd71f284ca4d44a668816193e4025c950b002252081e387433656ae9a70a82 \
--hash=sha256:84a007aae9b707fb0aebfc9da23db4b26fc9ab562eb56e335e9ec480cb19744f
# via
# -r docs/requirements.in
# mkdocstrings-python
@@ -364,9 +376,9 @@ mkdocstrings-python==1.16.12 \
--hash=sha256:22ded3a63b3d823d57457a70ff9860d5a4de9e8b1e482876fc9baabaf6f5f374 \
--hash=sha256:9b9eaa066e0024342d433e332a41095c4e429937024945fea511afe58f63175d
# via mkdocstrings
neoteroi-mkdocs==1.2.0 \
--hash=sha256:58e25cb1b9db093ffa8d12bdb33264bf567cac30fb964b56e0a493efa749ad6e \
--hash=sha256:91b6aa95a4e46c9bb93e00e021d2044cb0c7d80c0b4600434ff8f440d613a0a8
neoteroi-mkdocs==1.1.3 \
--hash=sha256:3ecfb825e898d10a6d703a3ef3f0484d823b7b5660425e76af421e316ac18036 \
--hash=sha256:772aee317c9bb10a89d67e71e322730f92cc349d5eecc8a08e8fb079398e514b
# via -r docs/requirements.in
packaging==25.0 \
--hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \
@@ -539,9 +551,12 @@ typing-extensions==4.14.1 \
# via
# anyio
# beautifulsoup4
urllib3==2.6.0 \
--hash=sha256:c90f7a39f716c572c4e3e58509581ebd83f9b59cced005b7db7ad2d22b0db99f \
--hash=sha256:cb9bcef5a4b345d5da5d145dc3e30834f58e8018828cbc724d30b4cb7d4d49f1
# exceptiongroup
# gitpython
# mkdocstrings-python
urllib3==2.5.0 \
--hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \
--hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc
# via requests
watchdog==6.0.0 \
--hash=sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a \
@@ -579,3 +594,7 @@ wcmatch==10.1 \
--hash=sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a \
--hash=sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af
# via mkdocs-include-markdown-plugin
zipp==3.23.0 \
--hash=sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e \
--hash=sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166
# via importlib-metadata

View File

@@ -97,12 +97,12 @@ skip-magic-trailing-comma = true
line-ending = "auto"
[tool.uv.pip]
python-version = "3.11.0"
python-version = "3.9.2"
no-strip-extras=true
generate-hashes=true
[tool.ty.environment]
root = ["src/backend/InvenTree"]
[tool.ty.src]
root = "src/backend/InvenTree"
[tool.ty.rules]
unresolved-reference="ignore" # 21 # see https://github.com/astral-sh/ty/issues/220
@@ -133,9 +133,3 @@ sections=["FUTURE","STDLIB","DJANGO","THIRDPARTY","FIRSTPARTY","LOCALFOLDER"]
[tool.codespell]
ignore-words-list = ["assertIn","SME","intoto","fitH"]
[tool.pytest]
django_find_project = false
pythonpath = ["src/backend/InvenTree"]
DJANGO_SETTINGS_MODULE = "InvenTree.settings"
python_files = ["test*.py",]

View File

@@ -11,7 +11,7 @@ python:
build:
os: "ubuntu-22.04"
tools:
python: "3.11"
python: "3.9"
jobs:
post_install:
- pip install -U invoke

View File

@@ -0,0 +1,79 @@
"""Middleware to require 2FA for users."""
from django.http import HttpRequest, HttpResponse, JsonResponse
from django.utils.deprecation import MiddlewareMixin
from django.utils.translation import gettext_lazy as _
from allauth.account.authentication import get_authentication_records
from allauth.mfa.utils import is_mfa_enabled
from allauth.mfa.webauthn.internal.flows import did_use_passwordless_login
def is_multifactor_logged_in(request: HttpRequest) -> bool:
"""Check if the user is logged in with multifactor authentication."""
authns = get_authentication_records(request)
return is_mfa_enabled(request.user) and (
did_use_passwordless_login(request)
or any(record.get('method') == 'mfa' for record in authns)
)
class AllUserRequire2FAMiddleware(MiddlewareMixin):
"""Ensure that users have two-factor authentication enabled before they have access restricted endpoints.
Adapted from https://github.com/pennersr/django-allauth/issues/3649
"""
allowed_pages = [
'api-user-meta',
'api-user-me',
'api-user-roles',
'api-inventree-info',
'api-token',
# web platform urls
'password_reset_confirm',
'web',
'web-wildcard',
'web-assets',
]
app_names = ['headless']
require_2fa_message = _(
'You must enable two-factor authentication before doing anything else.'
)
def on_require_2fa(self, request: HttpRequest) -> HttpResponse:
"""Force user to mfa activation."""
return JsonResponse(
{'id': 'mfa_register', 'error': self.require_2fa_message}, status=401
)
def is_allowed_page(self, request: HttpRequest) -> bool:
"""Check if the current page can be accessed without mfa."""
match = request.resolver_match
return (
None
if match is None
else any(ref in self.app_names for ref in match.app_names)
or match.url_name in self.allowed_pages
or match.route == 'favicon.ico'
)
def enforce_2fa(self, request):
"""Check if 2fa should be enforced for this request."""
return True
def process_view(
self, request: HttpRequest, view_func, view_args, view_kwargs
) -> HttpResponse:
"""If set up enforce 2fa registration."""
if request.user.is_anonymous:
return None
if self.is_allowed_page(request):
return None
if is_multifactor_logged_in(request):
return None
if self.enforce_2fa(request):
return self.on_require_2fa(request)
return None

View File

@@ -53,7 +53,7 @@ def read_license_file(path: Path) -> list:
return []
try:
data = json.loads(path.read_text(encoding='utf-8'))
data = json.loads(path.read_text())
except Exception as e:
logger.exception("Failed to parse license file '%s': %s", path, e)
return []
@@ -600,34 +600,6 @@ class BulkUpdateMixin(BulkOperationMixin):
return Response({'success': f'Updated {n} items'}, status=200)
class ParameterListMixin:
"""Mixin class which supports filtering against parametric fields."""
def filter_queryset(self, queryset):
"""Perform filtering against parametric fields."""
import common.filters
queryset = super().filter_queryset(queryset)
# Filter by parametric data
queryset = common.filters.filter_parametric_data(
queryset, self.request.query_params
)
serializer_class = (
getattr(self, 'serializer_class', None) or self.get_serializer_class()
)
model_class = serializer_class.Meta.model
# Apply ordering based on query parameter
queryset = common.filters.order_by_parameter(
queryset, model_class, self.request.query_params.get('ordering', None)
)
return queryset
class BulkDeleteMixin(BulkOperationMixin):
"""Mixin class for enabling 'bulk delete' operations for various models.

View File

@@ -1,66 +1,11 @@
"""InvenTree API version information."""
# InvenTree API version
INVENTREE_API_VERSION = 435
INVENTREE_API_VERSION = 421
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
v435 -> 2025-12-16 : https://github.com/inventree/InvenTree/pull/11030
- Adds token refresh endpoint to auth API
v434 -> 2025-12-16 : https://github.com/inventree/InvenTree/pull/11021
- The "tags" fields (on various API endpoints) is now optional, and disabled by default
- To request tags information, add "tags=true" to the API request query parameters
v433 -> 2025-12-16 : https://github.com/inventree/InvenTree/pull/11023
- "substitutes" field on the BomItem API endpoint is now excluded by default
- Add "?substitutes=true" query parameter to include substitute parts in BomItem API endpoint(s)
v432 -> 2025-12-15 : https://github.com/inventree/InvenTree/pull/11012
- The "part_detail" field on the SupplierPart API endpoint is now optional
- The "supplier_detail" field on the SupplierPart API endpoint is now optional
- The "manufacturer_detail" field on the ManufacturerPart API endpoint is now optional
- The "part_detail" field on the StockItem API is now disabled by default
v431 -> 2025-12-14 : https://github.com/inventree/InvenTree/pull/11006
- Remove duplicate "address" field on the Company API endpoint
- Make "primary_address" field optional on the Company API endpoint
- Remove "address_count" field from the Company API endpoint
v430 -> 2025-12-04 : https://github.com/inventree/InvenTree/pull/10699
- Removed the "PartParameter" and "PartParameterTemplate" API endpoints
- Removed the "ManufacturerPartParameter" API endpoint
- Added generic "Parameter" and "ParameterTemplate" API endpoints
v429 -> 2025-12-04 : https://github.com/inventree/InvenTree/pull/10938
- Adjust default values for currency codes in the API schema
- Note that this does not change any functional behavior, only the schema documentation
v428 -> 2025-11-28 : https://github.com/inventree/InvenTree/pull/10926
- Various typo fixes in API - no functional changes
v427 -> 2025-11-24 : https://github.com/inventree/InvenTree/pull/10896
- Fixes a spelling mistake in the API field labels
v426 -> 2025-11-19 : https://github.com/inventree/InvenTree/pull/10867
- Adds optional "price_breaks" filter to the SupplierPart API endpoint
v425 -> 2025-11-11 : https://github.com/inventree/InvenTree/pull/10802
- Adds "on_order" filter to the BuildLine API endpoint
- Allow BuildLine list to be ordered by "on_order" and "in_production" fields
v424 -> 2025-11-11 : 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
- Adds "category_detail" field to BomItem API endpoints
- Adds "category_detail" field to BuildLine API endpoints
v422 -> 2025-11-03 : https://github.com/inventree/InvenTree/pull/10750
- Adds ability to search StockItem API by supplier SKU
- Adds ability to search StockItem API by manufacturer MPN
v421 -> 2025-10-31 : https://github.com/inventree/InvenTree/pull/10724
- Allow upload of attachments against SupplierPart objects via the API
@@ -76,7 +21,7 @@ v418 -> 2025-10-24 : https://github.com/inventree/InvenTree/pull/10657
v417 -> 2025-10-22 : https://github.com/inventree/InvenTree/pull/10654
- Adds "checked" filter to SalesOrderShipment API endpoint
- Adds "order_status" filter to SalesOrderShipment API endpoint
- Adds "order_status" filter to SalesOrdereShipment API endpoint
- Adds "order_outstanding" filter to SalesOrderShipment API endpoint
v416 -> 2025-10-22 : https://github.com/inventree/InvenTree/pull/10651
@@ -134,7 +79,7 @@ v401 -> 2025-10-05 : https://github.com/inventree/InvenTree/pull/10381
- Adds machine properties to machine API endpoints
v400 -> 2025-10-05 : https://github.com/inventree/InvenTree/pull/10486
- Adds return datatypes for admin/config and flags endpoints
- Adds return datatypes for admin/config and flags entpoints
v399 -> 2025-10-05 : https://github.com/inventree/InvenTree/pull/10445
- Refactors 'customer_detail' param in SalesOrder API endpoint

View File

@@ -18,7 +18,6 @@ import InvenTree.conversion
import InvenTree.ready
import InvenTree.tasks
from InvenTree.config import get_setting
from InvenTree.ready import ignore_ready_warning
logger = structlog.get_logger('inventree')
MIGRATIONS_CHECK_DONE = False
@@ -71,7 +70,9 @@ class InvenTreeConfig(AppConfig):
InvenTree.tasks.offload_task(InvenTree.tasks.check_for_migrations)
self.update_site_url()
self.load_unit_registry()
# Ensure the unit registry is loaded
InvenTree.conversion.get_unit_registry()
if InvenTree.ready.canAppAccessDatabase() or settings.TESTING_ENV:
self.add_user_on_startup()
@@ -83,7 +84,6 @@ class InvenTreeConfig(AppConfig):
social_account_updated.connect(sso.ensure_sso_groups)
@ignore_ready_warning
def remove_obsolete_tasks(self):
"""Delete any obsolete scheduled tasks in the database."""
obsolete = [
@@ -112,7 +112,6 @@ class InvenTreeConfig(AppConfig):
except Exception:
logger.exception('Failed to remove obsolete tasks - database not ready')
@ignore_ready_warning
def start_background_tasks(self):
"""Start all background tests for InvenTree."""
logger.info('Starting background tasks...')
@@ -172,7 +171,6 @@ class InvenTreeConfig(AppConfig):
logger.info('Started %s scheduled background tasks...', len(tasks))
@ignore_ready_warning
def add_heartbeat(self):
"""Ensure there is at least one background task in the queue."""
import django_q.models
@@ -187,7 +185,6 @@ class InvenTreeConfig(AppConfig):
except Exception:
pass
@ignore_ready_warning
def collect_tasks(self):
"""Collect all background tasks."""
for app_name, app in apps.app_configs.items():
@@ -200,7 +197,6 @@ class InvenTreeConfig(AppConfig):
except Exception as e: # pragma: no cover
logger.exception('Error loading tasks for %s: %s', app_name, e)
@ignore_ready_warning
def update_site_url(self):
"""Update the site URL setting.
@@ -227,12 +223,6 @@ class InvenTreeConfig(AppConfig):
except Exception:
pass
@ignore_ready_warning
def load_unit_registry(self):
"""Ensure the unit registry is loaded."""
InvenTree.conversion.get_unit_registry()
@ignore_ready_warning
def add_user_on_startup(self):
"""Add a user on startup."""
# stop if checks were already created
@@ -287,11 +277,10 @@ class InvenTreeConfig(AppConfig):
new_user = user.objects.create_superuser(
add_user, add_email, add_password
)
logger.info('User %s was created!', new_user)
logger.info('User %s was created!', str(new_user))
except IntegrityError:
logger.warning('The user "%s" could not be created', add_user)
@ignore_ready_warning
def add_user_from_file(self):
"""Add the superuser from a file."""
# stop if checks were already created
@@ -332,18 +321,6 @@ class InvenTreeConfig(AppConfig):
return
if not InvenTree.tasks.check_for_migrations():
# Detect if this an empty database - if so, start with a fresh migration
if (
settings.DOCKER
and not InvenTree.ready.isInTestMode()
and not InvenTree.ready.isRunningMigrations()
and InvenTree.tasks.get_migration_count() == 0
):
logger.warning(
'INVE-W8: Empty database detected - trying to run migrations'
)
InvenTree.tasks.check_for_migrations(force_run=True)
else:
logger.error('INVE-W8: Database Migrations required')
sys.exit(1)
logger.error('INVE-W8: Database Migrations required')
sys.exit(1)
MIGRATIONS_CHECK_DONE = True

View File

@@ -2,6 +2,7 @@
import datetime
import time
from typing import Union
from django.conf import settings
from django.core.exceptions import ValidationError
@@ -108,7 +109,7 @@ class InvenTreeMailLoggingBackend(BaseEmailBackend):
return self.backend.open()
def send_messages(
self, email_messages: list[EmailMessage | EmailMultiAlternatives]
self, email_messages: list[Union[EmailMessage, EmailMultiAlternatives]]
) -> int:
"""Send email messages and log them to the database.

View File

@@ -4,8 +4,6 @@ import socket
import threading
from typing import Any
from django.db.utils import OperationalError, ProgrammingError
import structlog
import InvenTree.config
@@ -171,22 +169,3 @@ def set_session_cache(key: str, value: Any) -> None:
if request_cache is not None:
request_cache[key] = value
def get_cached_content_types(cache_key: str = 'all_content_types') -> list:
"""Return a list of all ContentType objects, using session cache if possible."""
from django.contrib.contenttypes.models import ContentType
# Attempt to retrieve a list of ContentType objects from session cache
if content_types := get_session_cache(cache_key):
return content_types
try:
content_types = list(ContentType.objects.all())
if len(content_types) > 0:
set_session_cache(cache_key, content_types)
except (OperationalError, ProgrammingError):
# Database is likely not yet ready
content_types = []
return content_types

View File

@@ -7,9 +7,8 @@ import os
import random
import shutil
import string
import sys
from pathlib import Path
from typing import Optional
from typing import Optional, Union
logger = logging.getLogger('inventree')
CONFIG_DATA = None
@@ -178,7 +177,7 @@ def get_config_file(create=True) -> Path:
return cfg_filename
def load_config_data(set_cache: bool = False) -> map | None:
def load_config_data(set_cache: bool = False) -> Union[map, None]:
"""Load configuration data from the config file.
Arguments:
@@ -196,15 +195,7 @@ def load_config_data(set_cache: bool = False) -> map | None:
cfg_file = get_config_file()
with open(cfg_file, encoding='utf-8') as cfg:
try:
data = yaml.safe_load(cfg)
except yaml.parser.ParserError as error:
logger.error(
"INVE-E13: Error reading InvenTree configuration file '%s': %s",
cfg_file,
error,
)
sys.exit(1)
data = yaml.safe_load(cfg)
# Set the cache if requested
if set_cache:
@@ -406,7 +397,7 @@ def get_plugin_dir():
return get_setting('INVENTREE_PLUGIN_DIR', 'plugin_dir')
def get_secret_key(return_path: bool = False) -> str | Path:
def get_secret_key(return_path: bool = False) -> Union[str, Path]:
"""Return the secret key value which will be used by django.
Following options are tested, in descending order of preference:
@@ -450,7 +441,7 @@ def get_secret_key(return_path: bool = False) -> str | Path:
return secret_key_file.read_text().strip()
def get_oidc_private_key(return_path: bool = False) -> str | Path:
def get_oidc_private_key(return_path: bool = False) -> Union[str, Path]:
"""Return the private key for OIDC authentication.
Following options are tested, in descending order of preference:

View File

@@ -1,96 +1,27 @@
"""Helper functions for converting between units."""
import logging
import re
from hashlib import md5
from typing import Optional
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
import pint
_unit_registry = None
import structlog
from common.settings import get_global_setting, set_global_setting
from InvenTree.cache import get_session_cache, set_session_cache
_UNIT_REG_CACHE_KEY = 'unit_registry_hash'
_unit_registry = None
_unit_registry_hash: str = ''
logger = structlog.get_logger('inventree')
# Disable log output for Pint library
logging.getLogger('pint').setLevel(logging.ERROR)
def can_cache_registry() -> bool:
"""Return True if it is appropriate to cache the unit registry.
Prevent caching under certain conditions (such as database migration)
to prevent database access.
"""
import InvenTree.ready
return not any([
InvenTree.ready.isImportingData(),
InvenTree.ready.isRunningBackup(),
InvenTree.ready.isRunningMigrations(),
InvenTree.ready.isInTestMode(),
])
def get_unit_registry_hash():
"""Return a hash representing the current state of the unit registry.
We use this to determine if we need to reload the unit registry,
due to changes in the database.
"""
# Look in the session cache first (faster, and potentially newer)
registry_hash = get_session_cache(_UNIT_REG_CACHE_KEY)
if registry_hash is None:
registry_hash = get_global_setting(
'_UNIT_REGISTRY_HASH', create=False, backup_value=''
)
if registry_hash:
set_session_cache(_UNIT_REG_CACHE_KEY, registry_hash)
return registry_hash
def set_unit_registry_hash(registry_hash: str):
"""Save the hash representing the current state of the unit registry.
Because most of the registry is static, we only need to consider the
CustomUnit entries in the database.
"""
global _unit_registry_hash
_unit_registry_hash = registry_hash
if not can_cache_registry():
return
# Save to both the global settings and the session cache
set_global_setting('_UNIT_REGISTRY_HASH', registry_hash)
set_session_cache(_UNIT_REG_CACHE_KEY, registry_hash)
def get_unit_registry():
"""Return a custom instance of the Pint UnitRegistry."""
global _unit_registry
global _unit_registry_hash
# Cache the unit registry for speedier access
if _unit_registry is None:
return reload_unit_registry()
# Check if the unit registry has changed
if can_cache_registry() and _unit_registry_hash != get_unit_registry_hash():
logger.info('Unit registry hash has changed, reloading unit registry')
return reload_unit_registry()
return _unit_registry
@@ -122,32 +53,23 @@ def reload_unit_registry():
reg.define('thousand = 1000')
# Allow for custom units to be defined in the database
# Calculate a hash of all custom units
hash_md5 = md5()
try:
from common.models import CustomUnit
custom_units = list(CustomUnit.objects.all())
for cu in CustomUnit.objects.all():
try:
reg.define(cu.fmt_string())
except Exception as e:
logger.exception(
'Failed to load custom unit: %s - %s', cu.fmt_string(), e
)
# Once custom units are loaded, save registry
_unit_registry = reg
except Exception:
# Database is likely not ready
custom_units = []
for cu in custom_units:
try:
fmt = cu.fmt_string()
reg.define(fmt)
hash_md5.update(fmt.encode('utf-8'))
except Exception as e:
logger.exception('Failed to load custom unit: %s - %s', cu.fmt_string(), e)
# Once custom units are loaded, save registry
_unit_registry = reg
# Update the unit registry hash
set_unit_registry_hash(hash_md5.hexdigest())
# Database is not ready, or CustomUnit model is not available
pass
dt = time.time() - t_start
logger.debug('Loaded unit registry in %.3f s', dt)

View File

@@ -22,7 +22,7 @@ class InvenTreeDateFilter(rest_filters.DateFilter):
if settings.USE_TZ and value is not None:
tz = timezone.get_current_timezone()
value = datetime(value.year, value.month, value.day)
value = make_aware(value, timezone=tz)
value = make_aware(value, timezone=tz, is_dst=True)
return super().filter(qs, value)

View File

@@ -5,10 +5,11 @@ import hashlib
import inspect
import io
import json
import os
import os.path
import re
from decimal import Decimal, InvalidOperation
from typing import Optional, TypeVar
from typing import Optional, TypeVar, Union
from wsgiref.util import FileWrapper
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
@@ -16,7 +17,6 @@ from django.conf import settings
from django.contrib.staticfiles.storage import StaticFilesStorage
from django.core.exceptions import FieldError, ValidationError
from django.core.files.storage import default_storage
from django.db.models.fields.files import FieldFile, ImageFieldFile
from django.http import StreamingHttpResponse
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
@@ -28,7 +28,6 @@ import structlog
from bleach import clean
from djmoney.money import Money
from PIL import Image
from stdimage.models import StdImageField, StdImageFieldFile
from common.currency import currency_code_default
@@ -128,7 +127,7 @@ def extract_int(
return ref_int
def generateTestKey(test_name: str | None) -> str:
def generateTestKey(test_name: Union[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.
Tests must be named such that they will have unique keys.
@@ -176,52 +175,11 @@ def constructPathString(path: list[str], max_chars: int = 250) -> str:
return pathstring
def getMediaUrl(
file: FieldFile | ImageFieldFile | StdImageFieldFile, name: str | None = None
):
def getMediaUrl(filename):
"""Return the qualified access path for the given file, under the media directory."""
if not isinstance(file, (FieldFile, ImageFieldFile, StdImageFieldFile)):
raise TypeError(
'file must be one of FileField, ImageFileField, StdImageFieldFile'
)
if name is not None:
file = regenerate_imagefile(file, name)
if settings.STORAGE_TARGET == StorageBackends.S3:
return str(file.url)
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
return str(filename)
return os.path.join(MEDIA_URL, str(filename))
def getStaticUrl(filename):

View File

@@ -1,6 +1,6 @@
"""Code for managing email functionality in InvenTree."""
from typing import Optional
from typing import Optional, Union
from django.conf import settings
@@ -8,6 +8,7 @@ import structlog
from allauth.account.models import EmailAddress
import InvenTree.ready
import InvenTree.tasks
from common.models import Priority, issue_mail
logger = structlog.get_logger('inventree')
@@ -63,7 +64,7 @@ def is_email_configured() -> bool:
def send_email(
subject: str,
body: str,
recipients: str | list,
recipients: Union[str, list],
from_email: Optional[str] = None,
html_message=None,
prio: Priority = Priority.NORMAL,

View File

@@ -1,9 +1,8 @@
"""Provides helper mixins that are used throughout the InvenTree project."""
import inspect
from collections.abc import Callable
from pathlib import Path
from typing import Any
from typing import Any, Callable
from django.conf import settings
from django.core.cache import cache

View File

@@ -24,13 +24,7 @@ from common.notifications import (
trigger_notification,
)
from common.settings import get_global_setting
from InvenTree.cache import (
get_cached_content_types,
get_session_cache,
set_session_cache,
)
from InvenTree.format import format_money
from InvenTree.ready import ignore_ready_warning
logger = structlog.get_logger('inventree')
@@ -266,7 +260,6 @@ def render_currency(
)
@ignore_ready_warning
def getModelsWithMixin(mixin_class) -> list:
"""Return a list of database models that inherit from the given mixin class.
@@ -275,23 +268,17 @@ def getModelsWithMixin(mixin_class) -> list:
Returns:
List of models that inherit from the given mixin class
"""
# First, look in the session cache - to prevent repeated expensive comparisons
cache_key = f'models_with_mixin_{mixin_class.__name__}'
from django.contrib.contenttypes.models import ContentType
if cached_models := get_session_cache(cache_key):
return cached_models
try:
db_models = [
x.model_class() for x in ContentType.objects.all() if x is not None
]
except (OperationalError, ProgrammingError):
# Database is likely not yet ready
db_models = []
content_types = get_cached_content_types()
db_models = [x.model_class() for x in content_types if x is not None]
models_with_mixin = [
x for x in db_models if x is not None and issubclass(x, mixin_class)
]
# Store the result in the session cache
set_session_cache(cache_key, models_with_mixin)
return models_with_mixin
return [x for x in db_models if x is not None and issubclass(x, mixin_class)]
def notify_responsible(

View File

@@ -35,6 +35,10 @@ def get_type_str(type_obj):
if type_obj.__module__ == 'builtins':
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__}'

View File

@@ -1,7 +1,7 @@
"""Extended schema generator."""
from pathlib import Path
from typing import TypeVar
from typing import TypeVar, Union
from django.conf import settings
@@ -26,7 +26,7 @@ def prep_name(ref):
return f'{dja_ref_prefix}.{ref}'
def sub_component_name(name: T) -> T | str:
def sub_component_name(name: T) -> Union[T, str]:
"""Clean up component references."""
if not isinstance(name, str):
return name
@@ -70,10 +70,8 @@ class Command(spectacular.Command):
# Reformat paths
for p_name, p_spec in spec['paths'].items():
# strip path name
p_name = (
p_name.removeprefix(dja_path_prefix)
.removeprefix('/_allauth/browser/v1/')
.removeprefix('/_allauth/app/v1/')
p_name = p_name.removeprefix(dja_path_prefix).removeprefix(
'/_allauth/browser/v1/'
)
# fix refs

View File

@@ -23,7 +23,7 @@ logger = structlog.get_logger('inventree')
class InvenTreeMetadata(SimpleMetadata):
"""Custom metadata class for the DRF API.
This custom metadata class limits the available "actions",
This custom metadata class imits the available "actions",
based on the user's role permissions.
Thus when a client send an OPTIONS request to an API endpoint,
@@ -50,10 +50,6 @@ class InvenTreeMetadata(SimpleMetadata):
for method in {'PUT', 'POST', 'GET'} & set(view.allowed_methods):
view.request = clone_request(request, method)
# Mark this request, to prevent expensive prefetching
view.request._metadata_requested = True
try:
# Test global permissions
if hasattr(view, 'check_permissions'):

View File

@@ -258,19 +258,6 @@ class OutputOptionsMixin:
return serializer
def get_queryset(self):
"""Return the queryset with output options applied.
This automatically applies any prefetching defined against the optional fields.
"""
queryset = super().get_queryset()
serializer = self.get_serializer()
if isinstance(serializer, FilterableSerializerMixin):
queryset = serializer.prefetch_queryset(queryset)
return queryset
class SerializerContextMixin:
"""Mixin to add context to serializer."""

View File

@@ -1,15 +1,12 @@
"""Generic models which provide extra functionality over base Django model types."""
from collections.abc import Callable
from datetime import datetime
from string import Formatter
from typing import Any, Optional
from typing import Optional
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.fields import GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import models, transaction
from django.db import models
from django.db.models import QuerySet
from django.db.models.signals import post_save
from django.db.transaction import TransactionManagementError
@@ -23,7 +20,6 @@ from django_q.models import Task
from error_report.models import Error
from mptt.exceptions import InvalidMove
from mptt.models import MPTTModel, TreeForeignKey
from stdimage.models import StdImageField
import common.settings
import InvenTree.exceptions
@@ -169,14 +165,10 @@ class MetadataMixin(models.Model):
abstract = True
def save(self, force_insert=False, force_update=False, *args, **kwargs):
def save(self, *args, **kwargs):
"""Save the model instance, and perform validation on the metadata field."""
self.validate_metadata()
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)
super().save(*args, **kwargs)
def clean(self, *args, **kwargs):
"""Perform model validation on the metadata field."""
@@ -452,18 +444,7 @@ class ReferenceIndexingMixin(models.Model):
reference_int = models.BigIntegerField(default=0)
class ContentTypeMixin:
"""Mixin class which supports retrieval of the ContentType for a model instance."""
@classmethod
def get_content_type(cls):
"""Return the ContentType object associated with this model."""
from django.contrib.contenttypes.models import ContentType
return ContentType.objects.get_for_model(cls)
class InvenTreeModel(ContentTypeMixin, PluginValidationMixin, models.Model):
class InvenTreeModel(PluginValidationMixin, models.Model):
"""Base class for InvenTree models, which provides some common functionality.
Includes the following mixins by default:
@@ -486,167 +467,7 @@ class InvenTreeMetadataModel(MetadataMixin, InvenTreeModel):
abstract = True
class InvenTreePermissionCheckMixin:
"""Provides an abstracted class for managing permissions against related fields."""
@classmethod
def check_related_permission(cls, permission, user) -> bool:
"""Check if the user has permission to perform the specified action on the attachment.
The default implementation runs a permission check against *this* model class,
but this can be overridden in the implementing class if required.
Arguments:
permission: The permission to check (add / change / view / delete)
user: The user to check against
Returns:
bool: True if the user has permission, False otherwise
"""
perm = f'{cls._meta.app_label}.{permission}_{cls._meta.model_name}'
return user.has_perm(perm)
class InvenTreeParameterMixin(InvenTreePermissionCheckMixin, models.Model):
"""Provides an abstracted class for managing parameters.
Links the implementing model to the common.models.Parameter table,
and provides the following methods:
"""
class Meta:
"""Metaclass options for InvenTreeParameterMixin."""
abstract = True
# Define a reverse relation to the Parameter model
parameters_list = GenericRelation(
'common.Parameter', content_type_field='model_type', object_id_field='model_id'
)
@staticmethod
def annotate_parameters(queryset: QuerySet) -> QuerySet:
"""Annotate a queryset with pre-fetched parameters.
Args:
queryset: Queryset to annotate
Returns:
Annotated queryset
"""
return queryset.prefetch_related(
'parameters_list',
'parameters_list__model_type',
'parameters_list__updated_by',
'parameters_list__template',
'parameters_list__template__model_type',
)
@property
def parameters(self) -> QuerySet:
"""Return a QuerySet containing all the Parameter instances for this model.
This will return pre-fetched data if available (i.e. in a serializer context).
"""
# Check the query cache for pre-fetched parameters
if cache := getattr(self, '_prefetched_objects_cache', None):
if 'parameters_list' in cache:
return cache['parameters_list']
return self.parameters_list.all()
def delete(self, *args, **kwargs):
"""Handle the deletion of a model instance.
Before deleting the model instance, delete any associated parameters.
"""
self.parameters_list.all().delete()
super().delete(*args, **kwargs)
@transaction.atomic
def copy_parameters_from(self, other, clear=True, **kwargs):
"""Copy all parameters from another model instance.
Arguments:
other: The other model instance to copy parameters from
clear: If True, clear existing parameters before copying
**kwargs: Additional keyword arguments to pass to the Parameter constructor
"""
import common.models
if clear:
self.parameters_list.all().delete()
parameters = []
content_type = ContentType.objects.get_for_model(self.__class__)
template_ids = [parameter.template.pk for parameter in other.parameters.all()]
# Remove all conflicting parameters first
self.parameters_list.filter(template__pk__in=template_ids).delete()
for parameter in other.parameters.all():
parameter.pk = None
parameter.model_id = self.pk
parameter.model_type = content_type
parameters.append(parameter)
if len(parameters) > 0:
common.models.Parameter.objects.bulk_create(parameters)
def get_parameter(self, name: str):
"""Return a Parameter instance for the given parameter name.
Args:
name: Name of the parameter template
Returns:
Parameter instance if found, else None
"""
return self.parameters_list.filter(template__name=name).first()
def get_parameters(self) -> QuerySet:
"""Return all Parameter instances for this model."""
return (
self.parameters_list.all()
.prefetch_related('template', 'model_type')
.order_by('template__name')
)
def parameters_map(self) -> dict:
"""Return a map (dict) of parameter values associated with this Part instance, of the form.
Example:
{
"name_1": "value_1",
"name_2": "value_2",
}
"""
params = {}
for parameter in self.parameters.all().prefetch_related('template'):
params[parameter.template.name] = parameter.data
return params
def check_parameter_delete(self, parameter):
"""Run a check to determine if the provided parameter can be deleted.
The default implementation always returns True, but this can be overridden in the implementing class.
"""
return True
def check_parameter_save(self, parameter):
"""Run a check to determine if the provided parameter can be saved.
The default implementation always returns True, but this can be overridden in the implementing class.
"""
return True
class InvenTreeAttachmentMixin(InvenTreePermissionCheckMixin):
class InvenTreeAttachmentMixin:
"""Provides an abstracted class for managing file attachments.
Links the implementing model to the common.models.Attachment table,
@@ -664,15 +485,33 @@ class InvenTreeAttachmentMixin(InvenTreePermissionCheckMixin):
super().delete(*args, **kwargs)
@property
def attachments(self) -> QuerySet:
def attachments(self):
"""Return a queryset containing all attachments for this model."""
return self.attachments_for_model().filter(model_id=self.pk)
def attachments_for_model(self) -> QuerySet:
@classmethod
def check_attachment_permission(cls, permission, user) -> bool:
"""Check if the user has permission to perform the specified action on the attachment.
The default implementation runs a permission check against *this* model class,
but this can be overridden in the implementing class if required.
Arguments:
permission: The permission to check (add / change / view / delete)
user: The user to check against
Returns:
bool: True if the user has permission, False otherwise
"""
perm = f'{cls._meta.app_label}.{permission}_{cls._meta.model_name}'
return user.has_perm(perm)
def attachments_for_model(self):
"""Return all attachments for this model class."""
from common.models import Attachment
model_type = self.__class__.__name__.lower()
return Attachment.objects.filter(model_type=model_type)
def create_attachment(self, attachment=None, link=None, comment='', **kwargs):
@@ -688,7 +527,7 @@ class InvenTreeAttachmentMixin(InvenTreePermissionCheckMixin):
Attachment.objects.create(**kwargs)
class InvenTreeTree(ContentTypeMixin, MPTTModel):
class InvenTreeTree(MPTTModel):
"""Provides an abstracted self-referencing tree model, based on the MPTTModel class.
Our implementation provides the following key improvements:
@@ -1463,55 +1302,3 @@ def after_error_logged(sender, instance: Error, created: bool, **kwargs):
'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()

View File

@@ -149,7 +149,7 @@ class InvenTreeRoleScopeMixin(OASTokenMixin):
class InvenTreeTokenMatchesOASRequirements(InvenTreeRoleScopeMixin):
"""Combines InvenTree role-based scope handling with OpenAPI schema token requirements.
Used as default permission class.
Usesd as default permission class.
"""
def has_permission(self, request, view):
@@ -166,29 +166,6 @@ class InvenTreeTokenMatchesOASRequirements(InvenTreeRoleScopeMixin):
return True
class ModelPermission(permissions.DjangoModelPermissions):
"""Custom ModelPermission implementation which provides cached lookup of queryset.
This is entirely for optimization purposes.
"""
def _queryset(self, view):
"""Return the queryset associated with this view, with caching.
This is because in a metadata OPTIONS request, the view is copied multiple times.
We can cache the queryset to avoid repeated calculation.
"""
if getattr(view, '_cached_queryset', None) is not None:
return view._cached_queryset
queryset = super()._queryset(view)
if queryset is not None:
view._cached_queryset = queryset
return queryset
class RolePermission(InvenTreeRoleScopeMixin, permissions.BasePermission):
"""Role mixin for API endpoints, allowing us to specify the user "role" which is required for certain operations.

View File

@@ -1,130 +0,0 @@
"""Helper functions for profiling InvenTree code.
A set of decorators to assist with profiling functions and logging,
which implement oft-repeated patterns used during development and debugging.
Note: These functions are not to be used in production code.
"""
from functools import wraps
def ensure_debug():
"""Ensure that InvenTree is running in DEBUG mode."""
from django.conf import settings
if not settings.DEBUG:
raise RuntimeError('Profiling functions can only be used in DEBUG mode!')
def time_function(func): # pragma: no cover
"""Decorator to time a function's execution duration.
Args:
func: Function to be timed
"""
@wraps(func)
def wrapper(*args, **kwargs):
import time
ensure_debug()
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
duration = end_time - start_time
print(f"Function '{func.__name__}' executed in {duration:.6f} seconds.")
return result
return wrapper
def profile_function(filename='profile.prof'): # pragma: no cover
"""Decorator to profile a function using cProfile.
Args:
func: Function to be profiled
filename: Output filename for the profiling data
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
import cProfile
import io
import pstats
ensure_debug()
pr = cProfile.Profile()
pr.enable()
result = func(*args, **kwargs)
pr.disable()
s = io.StringIO()
sortby = pstats.SortKey.CUMULATIVE
ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
ps.dump_stats(filename)
print(s.getvalue())
return result
return wrapper
return decorator
def log_slow_queries(
threshold: float = 0.01, n: int = 5, log_to_file: bool = True
): # pragma: no cover
"""Decorator to log slow database queries in a Django view function.
Args:
func: Function to be decorated
threshold: Time threshold (in seconds) for logging slow queries
n: Number of slowest queries to log
log_to_file: Whether to log to a file or print to console
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
from django.db import connection
ensure_debug()
result = func(*args, **kwargs)
slow_queries = [
q for q in connection.queries if float(q.get('time', 0)) >= threshold
]
slow_queries.sort(key=lambda x: float(x.get('time', 0)), reverse=True)
log_entries = []
for query in slow_queries[:n]:
log_entry = f'Time: {query["time"]}s | SQL: {query["sql"]}'
log_entries.append(log_entry)
if log_entries:
log_message = '\n'.join(log_entries)
if log_to_file:
with open('slow_queries.log', 'w', encoding='utf-8') as f:
f.write(f'Slow queries detected:\n{log_message}\n')
else:
print(f'Slow queries detected:\n{log_message}')
return result
return wrapper
return decorator
# Raise an exception if this file is imported outside of DEBUG mode
ensure_debug()

View File

@@ -1,36 +1,13 @@
"""Functions to check if certain parts of InvenTree are ready."""
import functools
import inspect
import os
import sys
import warnings
# Keep track of loaded apps, to prevent multiple executions of ready functions
_loaded_apps = set()
def clearLoadedApps():
"""Clear the set of loaded apps."""
global _loaded_apps
_loaded_apps = set()
def setAppLoaded(app_name: str):
"""Mark an app as loaded."""
global _loaded_apps
_loaded_apps.add(app_name)
def isAppLoaded(app_name: str) -> bool:
"""Return True if the app has been marked as loaded."""
global _loaded_apps
return app_name in _loaded_apps
def isInTestMode():
"""Returns True if the database is in testing mode."""
return 'test' in sys.argv or sys.argv[0].endswith('pytest')
return 'test' in sys.argv
def isImportingData():
@@ -180,22 +157,3 @@ def isPluginRegistryLoaded():
from plugin import registry
return registry.plugins_loaded
def ignore_ready_warning(func):
"""Decorator to ignore 'AppRegistryNotReady' warnings in functions called during app ready phase.
Ref: https://github.com/inventree/InvenTree/issues/10806
"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
with warnings.catch_warnings():
warnings.filterwarnings(
'ignore',
message='Accessing the database during app initialization is discouraged',
category=RuntimeWarning,
)
return func(*args, **kwargs)
return wrapper

View File

@@ -1,7 +1,7 @@
"""Schema processing functions for cleaning up generated schema."""
from itertools import chain
from typing import Any, Optional
from typing import Optional
from django.conf import settings
@@ -138,43 +138,6 @@ class ExtendedAutoSchema(AutoSchema):
return operation
def postprocess_schema_enums(result, generator, **kwargs):
"""Override call to drf-spectacular's enum postprocessor to filter out specific warnings."""
from drf_spectacular import drainage
# Monkey-patch the warn function temporarily
original_warn = drainage.warn
def custom_warn(msg: str, delayed: Any = None) -> None:
"""Custom patch to ignore some drf-spectacular warnings.
- Some warnings are unavoidable due to the way that InvenTree implements generic relationships (via ContentType).
- The cleanest way to handle this appears to be to override the 'warn' function from drf-spectacular.
Ref: https://github.com/inventree/InvenTree/pull/10699
"""
ignore_patterns = [
'enum naming encountered a non-optimally resolvable collision for fields named "model_type"'
]
if any(pattern in msg for pattern in ignore_patterns):
return
original_warn(msg, delayed)
# Replace the warn function with our custom version
drainage.warn = custom_warn
import drf_spectacular.hooks
result = drf_spectacular.hooks.postprocess_schema_enums(result, generator, **kwargs)
# Restore the original warn function
drainage.warn = original_warn
return result
def postprocess_required_nullable(result, generator, request, public):
"""Un-require nullable fields.

Some files were not shown because too many files have changed in this diff Show More