Merge branch 'master' into select_and_focus_qty_in_stock_add_count_remove
|
|
@ -185,14 +185,14 @@ jobs:
|
|||
fi
|
||||
- name: Login to Dockerhub
|
||||
if: github.event_name != 'pull_request' && steps.docker_login.outputs.skip_dockerhub_login != 'true'
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # pin@v4.0.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # pin@v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Log into registry ghcr.io
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # pin@v4.0.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # pin@v4.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ jobs:
|
|||
- name: Export API Documentation
|
||||
run: invoke dev.schema --ignore-warnings --filename src/backend/InvenTree/schema.yml
|
||||
- name: Upload schema
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # pin@v7.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # pin@v7.0.1
|
||||
with:
|
||||
name: schema.yml
|
||||
path: src/backend/InvenTree/schema.yml
|
||||
|
|
@ -222,7 +222,7 @@ jobs:
|
|||
echo "Downloaded api.yaml"
|
||||
- name: Running OpenAPI Spec diff action
|
||||
id: breaking_changes
|
||||
uses: oasdiff/oasdiff-action/diff@1f38ea5ea0b4a2e4e49901c3bcdf4386a05e9ea1 # pin@main
|
||||
uses: oasdiff/oasdiff-action/diff@f8cb9308b42121e793f835bd14c0b8090420430c # pin@main
|
||||
with:
|
||||
base: "api.yaml"
|
||||
revision: "src/backend/InvenTree/schema.yml"
|
||||
|
|
@ -251,17 +251,17 @@ jobs:
|
|||
- name: Extract settings / tags
|
||||
run: invoke int.export-definitions --basedir docs
|
||||
- name: Upload settings
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # pin@v7.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # pin@v7.0.1
|
||||
with:
|
||||
name: inventree_settings.json
|
||||
path: docs/generated/inventree_settings.json
|
||||
- name: Upload tags
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # pin@v7.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # pin@v7.0.1
|
||||
with:
|
||||
name: inventree_tags.yml
|
||||
path: docs/generated/inventree_tags.yml
|
||||
- name: Upload filters
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # pin@v7.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # pin@v7.0.1
|
||||
with:
|
||||
name: inventree_filters.yml
|
||||
path: docs/generated/inventree_filters.yml
|
||||
|
|
@ -363,7 +363,7 @@ jobs:
|
|||
pip install .
|
||||
if: needs.paths-filter.outputs.submit-performance == 'true'
|
||||
- name: Performance Reporting
|
||||
uses: CodSpeedHQ/action@1c8ae4843586d3ba879736b7f6b7b0c990757fab # pin@v4
|
||||
uses: CodSpeedHQ/action@658a901452bb54c799643e060733b7afe9121b8d # pin@v4.14.0
|
||||
# check if we are in inventree/inventree - reporting only works in that OIDC context
|
||||
if: github.repository == 'inventree/InvenTree' && needs.paths-filter.outputs.submit-performance == 'true'
|
||||
with:
|
||||
|
|
@ -409,7 +409,7 @@ jobs:
|
|||
- name: Coverage Tests
|
||||
run: invoke dev.test --check --coverage --translations
|
||||
- name: Upload raw coverage to artifacts
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # pin@v7.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # pin@v7.0.1
|
||||
with:
|
||||
name: coverage
|
||||
path: .coverage
|
||||
|
|
@ -454,7 +454,7 @@ jobs:
|
|||
env:
|
||||
node_version: '>=20.19.0'
|
||||
- name: Performance Reporting
|
||||
uses: CodSpeedHQ/action@1c8ae4843586d3ba879736b7f6b7b0c990757fab # pin@v4
|
||||
uses: CodSpeedHQ/action@658a901452bb54c799643e060733b7afe9121b8d # pin@v4.14.0
|
||||
with:
|
||||
mode: walltime
|
||||
run: inv dev.test --pytest
|
||||
|
|
@ -725,7 +725,7 @@ jobs:
|
|||
invoke static
|
||||
env INVENTREE_CUSTOM_SPLASH="img/playwright_custom_splash.png" INVENTREE_CUSTOM_LOGO="img/playwright_custom_logo.png" npx nyc playwright test --project=customization
|
||||
npx nyc playwright test --project=chromium --project=firefox
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # pin@v7.0.0
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # pin@v7.0.1
|
||||
if: ${{ !cancelled() && steps.tests.outcome == 'failure' }}
|
||||
with:
|
||||
name: playwright-report
|
||||
|
|
@ -770,7 +770,7 @@ jobs:
|
|||
run: |
|
||||
cd src/backend/InvenTree/web/static
|
||||
zip -r frontend-build.zip web/ web/.vite
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # pin@v7.0.0
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # pin@v7.0.1
|
||||
with:
|
||||
name: frontend-build
|
||||
path: src/backend/InvenTree/web/static/web
|
||||
|
|
@ -795,7 +795,7 @@ jobs:
|
|||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Upload SARIF file
|
||||
uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # pin@v3
|
||||
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # pin@v3
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
category: zizmor
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
pip install --require-hashes -r contrib/dev_reqs/requirements.txt
|
||||
python3 .github/scripts/version_check.py
|
||||
- name: Push to Stable Branch
|
||||
uses: ad-m/github-push-action@77c5b412c50b723d2a4fbc6d71fb5723bcd439aa # pin@v1.0.0
|
||||
uses: ad-m/github-push-action@4cc74773234f74829a8c21bc4d69dd4be9cfa599 # pin@v1.1.0
|
||||
if: env.stable_release == 'true'
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
@ -86,7 +86,7 @@ jobs:
|
|||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
- name: Upload frontend to artifacts
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # pin@v7.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # pin@v7.0.1
|
||||
with:
|
||||
name: frontend-build
|
||||
path: src/backend/InvenTree/web/static/frontend-build.zip
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ jobs:
|
|||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
|
|
@ -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@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
|
|
|||
|
|
@ -97,3 +97,4 @@ repos:
|
|||
rev: 0.4.3
|
||||
hooks:
|
||||
- id: teyit
|
||||
language_version: python3.11
|
||||
|
|
|
|||
|
|
@ -11,8 +11,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Added
|
||||
|
||||
- [#11853](https://github.com/inventree/InvenTree/pull/11853) adds BOM comparison functionality, allowing users to compare the BOM of one assembly with another assembly.
|
||||
- [#11809](https://github.com/inventree/InvenTree/pull/11809) adds multi-level subassembly display mode to the BOM table, allowing users to view multiple levels of subassemblies in a single table view. This is an optional display mode which can be toggled on or off by the user.
|
||||
- [#11778](https://github.com/inventree/InvenTree/pull/11778) adds inline supplier part creation to po line item addition dialog.
|
||||
- [#11772](https://github.com/inventree/InvenTree/pull/11772) the UI now warns if you navigate away from a note panel with unsaved changes
|
||||
- [#11788](https://github.com/inventree/InvenTree/pull/11788) adds support for custom permissions checks on database models defined in plugins. If a model defines a `check_user_permission` classmethod, this will be called to determine if a user has permission to view the model. This is required for plugin models which do not have the required ruleset definitions for the standard permission system.
|
||||
|
||||
### Changed
|
||||
|
||||
- [#11825](https://github.com/inventree/InvenTree/pull/11825) adds a new "bom" ruleset and associated permissions for BOM management, separate from the "part" ruleset which remains focused on part management. This allows for more granular control over user permissions, allowing users to have different levels of access to part management and BOM management functionality.
|
||||
- [#11816](https://github.com/inventree/InvenTree/pull/11816) makes the `issued_by` field on the `Build` API read only, and instead sets the `issued_by` field to the current user when a build is created. This change was made to ensure that the `issued_by` field accurately reflects the user who created the build, and to prevent users from setting this field to an arbitrary value when creating or updating a build.
|
||||
|
||||
### Removed
|
||||
|
||||
## 1.3.0 - 2026-04-11
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ gunicorn==25.3.0 \
|
|||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# -r contrib/container/requirements.in
|
||||
invoke==2.2.1 \
|
||||
--hash=sha256:2413bc441b376e5cd3f55bb5d364f973ad8bdd7bf87e53c79de3c11bf3feecc8 \
|
||||
--hash=sha256:515bf49b4a48932b79b024590348da22f39c4942dff991ad1fb8b8baea1be707
|
||||
invoke==3.0.3 \
|
||||
--hash=sha256:437b6a622223824380bfb4e64f612711a6b648c795f565efc8625af66fb57f0c \
|
||||
--hash=sha256:f11327165e5cbb89b2ad1d88d3292b5113332c43b8553b494da435d6ec6f5053
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# -r contrib/container/requirements.in
|
||||
|
|
@ -53,9 +53,9 @@ mysqlclient==2.2.8 \
|
|||
--hash=sha256:9bed7c8d3b629bdc09e17fb628d5b3b0a5fd1f12b09432b464b9126c727bedc0 \
|
||||
--hash=sha256:a81f5e12f8d05439709cb02fba97f9f76d1a6c528164f2260d8798fec969e300
|
||||
# via -r contrib/container/requirements.in
|
||||
packaging==26.0 \
|
||||
--hash=sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4 \
|
||||
--hash=sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529
|
||||
packaging==26.1 \
|
||||
--hash=sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f \
|
||||
--hash=sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# gunicorn
|
||||
|
|
@ -236,26 +236,26 @@ typing-extensions==4.15.0 \
|
|||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# psycopg-pool
|
||||
uv==0.10.12 \
|
||||
--hash=sha256:006812a086fce03d230fc987299f7295c7a73d17a1f1c17de1d1f327826f8481 \
|
||||
--hash=sha256:101481a1f48db6becf219914a591a588c0b3bfd05bef90768a5d04972bd6455e \
|
||||
--hash=sha256:2ace05115bd9ee1b30d341728257fe051817c4c0a652c085c90d4bd4fb0bc8f2 \
|
||||
--hash=sha256:2bb5893d79179727253e4a283871a693d7773c662a534fb897aa65496aa35765 \
|
||||
--hash=sha256:2c21e1b36c384f75dd3fd4a818b04871158ce115efff0bb4fdcd18ba2df7bd48 \
|
||||
--hash=sha256:2c5dfc7560453186e911c8c2e4ce95cd1c91e1c5926c3b34c5a825a307217be9 \
|
||||
--hash=sha256:384b7f36a1ae50efe5f50fe299f276a83bf7acc8b7147517f34e27103270f016 \
|
||||
--hash=sha256:551f799d53e397843b6cde7e3c61de716fb487da512a21a954b7d0cbc06967e0 \
|
||||
--hash=sha256:6727e3a0208059cd4d621684e580d5e254322dacbd806e0d218360abd0d48a68 \
|
||||
--hash=sha256:7099bdefffbe2df81accad52579657b8f9f870170caa779049c9fd82d645c9b3 \
|
||||
--hash=sha256:76ebe11572409dfbe20ec25a823f9bc8781400ece5356aa33ec44903af7ec316 \
|
||||
--hash=sha256:8dc352c93a47a4760cf824c31c55ce26511af780481e8f67c796d2779acaa928 \
|
||||
--hash=sha256:a5afe619e8a861fe4d49df8e10d2c6963de0dac6b79350c4832bf3366c8496cf \
|
||||
--hash=sha256:b9ca1d264059cb016c853ebbc4f21c72d983e0f347c927ca29e283aec2f596cf \
|
||||
--hash=sha256:bd84379292e3c1a1bf0a05847c7c72b66bb581dccf8da1ef94cc82bf517efa7c \
|
||||
--hash=sha256:be85acae8f31c68311505cd96202bad43165cbd7be110c59222f918677e93248 \
|
||||
--hash=sha256:cca36540d637c80d11d8a44a998a068355f0c78b75ec6b0f152ecbf89dfdd67b \
|
||||
--hash=sha256:e0f0ef58f0ba6fbfaf5f91b67aad6852252c49b8f78015a2a5800cf74c7538d5 \
|
||||
--hash=sha256:fa722691c7ae5c023778ad0b040ab8619367bcfe44fd0d9e05a58751af86cdf8
|
||||
uv==0.11.7 \
|
||||
--hash=sha256:0df59ab0c6a4b14a763e8445e1c303af9abeb53cdfa4428daf9ff9642c0a3cce \
|
||||
--hash=sha256:162fa961a9a081dcea6e889c79f738a5ae56507047e4672964972e33c301bea9 \
|
||||
--hash=sha256:23d457d6731ebdb83f1bffebe4894edab2ef43c1ec5488433c74300db4958924 \
|
||||
--hash=sha256:46d971489b00bdb27e0aa715e4a5cd4ef2c28ea5b6ef78f2b67bf861eb44b405 \
|
||||
--hash=sha256:4e4d5e31bea86e1b6e0f5a0f95e14e80018e6f6c0129256d2915a4b3d793644d \
|
||||
--hash=sha256:553e67cc766d013ce24353fecd4ea5533d2aedcfd35f9fac430e07b1d1f23ed4 \
|
||||
--hash=sha256:5674dfb5944513f4b3735b05c2deba6b1b01151f46729d533d413a9a905f8c5d \
|
||||
--hash=sha256:5985a15a92bd9a170fc1947abb1fbc3e9828c5a430ad85b5bed8356c20b67a71 \
|
||||
--hash=sha256:6158b7e39464f1aa1e040daa0186cae4749a78b5cd80ac769f32ca711b8976b1 \
|
||||
--hash=sha256:750ee5b96959b807cf442b73dd8b55111862d63f258f896787ea5f06b68aaca9 \
|
||||
--hash=sha256:7d6a17507b8139b8803f445a03fd097f732ce8356b1b7b13cdb4dd8ef7f4b2e0 \
|
||||
--hash=sha256:8b2fe1ec6775dad10183e3fdce430a5b37b7857d49763c884f3a67eaa8ca6f8a \
|
||||
--hash=sha256:ceae53b202ea92bc954759bc7c7570cdcd5c3512fce15701198c19fd2dfb8605 \
|
||||
--hash=sha256:dd48823ca4b505124389f49ae50626ba9f57212b9047738efc95126ed5f3844d \
|
||||
--hash=sha256:eb91f52ee67e10d5290f2c2897e2171357f1a10966de38d83eefa93d96843b0c \
|
||||
--hash=sha256:f394331f0507e80ee732cb3df737589de53bed999dd02a6d24682f08c2f8ac4f \
|
||||
--hash=sha256:f422d39530516b1dfb28bb6e90c32bb7dacd50f6a383cd6e40c1a859419fbc8c \
|
||||
--hash=sha256:f97e9f4e4d44fb5c4dfaa05e858ef3414a96416a2e4af270ecd88a3e5fb049a9 \
|
||||
--hash=sha256:fab0bb43fbbc0ee5b5fee212078d2300c371b725faff7cf72eeaafa0bff0606b
|
||||
# via -r contrib/container/requirements.in
|
||||
wheel==0.46.3 \
|
||||
--hash=sha256:4b399d56c9d9338230118d705d9737a2a468ccca63d5e813e2a4fc7815d8bc4d \
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
title: Build Orders
|
||||
---
|
||||
|
||||
## Build Order List
|
||||
|
||||
The build order list display lists all build orders:
|
||||
|
||||
{{ image("app/build_list.png", "Build order list") }}
|
||||
|
||||
Select an individual build order to display the detail view for that order.
|
||||
|
||||
### Filtering
|
||||
|
||||
Displayed build orders can be subsequently filtered using the search input at the top of the screen
|
||||
|
||||
## Build Order Detail
|
||||
|
||||
{{ image("app/build_detail.png", "Build order detail") }}
|
||||
|
||||
### Edit Build Order Details
|
||||
|
||||
From the detail view, select the *Edit* button in the top-right of the screen. This opens the build order editing display.
|
||||
|
||||
### Required Parts Tab
|
||||
|
||||
The *Required Parts* tab shows the parts required to complete this build order:
|
||||
|
||||
{{ image("app/build_required_parts.png", "Build order required parts") }}
|
||||
|
||||
### Build Outputs Tab
|
||||
|
||||
The *Build Outputs* tab shows the stock items which have been produced as part of this build order:
|
||||
|
||||
{{ image("app/build_outputs.png", "Build order outputs") }}
|
||||
|
|
@ -30,7 +30,7 @@ The *Line Items* tab shows the line items associated with this purchase order:
|
|||
|
||||
{{ image("app/po_lines.png", "Purchase order line items") }}
|
||||
|
||||
Long press on a particular line item to receive the item into stock.
|
||||
Select a particular line item to view the details for that line, and receive the line item into stock.
|
||||
|
||||
### Stock Items
|
||||
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 266 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
|
@ -195,3 +195,14 @@ This means that either:
|
|||
- The docker user does not have write permission to the specified directory
|
||||
|
||||
In either case, ensure that the directory is available *on your local machine* and the user account has the required permissions.
|
||||
|
||||
|
||||
## Error Rendering Component
|
||||
|
||||
Sometimes, following a software update, you may find that certain components of the web interface are not rendering correctly, and presented with a message similar to the screenshow below:
|
||||
|
||||
{{ image("faq/boundary.png", "Error Rendering Component") }}
|
||||
|
||||
This is often due to a caching issue with your web browser. Try performing a hard refresh of the page to clear the cache, this should resolve the issue in most cases.
|
||||
|
||||
If the problem persists, refer to the [troubleshooting guide](./troubleshooting.md) for further assistance.
|
||||
|
|
|
|||
|
|
@ -280,9 +280,27 @@ def on_post_build(*args, **kwargs):
|
|||
ignored_settings = {
|
||||
'global': ['SERVER_RESTART_REQUIRED'],
|
||||
'user': ['LAST_USED_PRINTING_MACHINES'],
|
||||
'config': [
|
||||
'INVENTREE_DB_TCP_KEEPALIVES',
|
||||
'INVENTREE_DB_TCP_KEEPALIVES_IDLE',
|
||||
'INVENTREE_DB_TCP_KEEPALIVES_INTERVAL',
|
||||
'INVENTREE_DB_TCP_KEEPALIVES_COUNT',
|
||||
'INVENTREE_DB_ISOLATION_SERIALIZABLE',
|
||||
'INVENTREE_DB_WAL_MODE',
|
||||
'INVENTREE_PLUGIN_DIR',
|
||||
'INVENTREE_DOCKER',
|
||||
'INVENTREE_FLAGS',
|
||||
'INVENTREE_REMOTE_LOGIN',
|
||||
'INVENTREE_REMOTE_LOGIN_HEADER',
|
||||
'TEST_TRANSLATIONS',
|
||||
'INVENTREE_FRONTEND_URL_BASE',
|
||||
'INVENTREE_FRONTEND_API_HOST',
|
||||
'INVENTREE_FRONTEND_SETTINGS',
|
||||
'INVENTREE_LOGOUT_REDIRECT_URL',
|
||||
],
|
||||
}
|
||||
|
||||
for group in ['global', 'user']:
|
||||
for group in ['global', 'user', 'config']:
|
||||
expected = expected_settings.get(group, {})
|
||||
observed = observed_settings.get(group, {})
|
||||
ignored = ignored_settings.get(group, [])
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ title: Bill of Materials
|
|||
|
||||
## Bill of Materials
|
||||
|
||||
A Bill of Materials (BOM) defines the list of component parts required to make an assembly, [create builds](./build.md) and allocate inventory.
|
||||
A Bill of Materials (BOM) defines the list of component parts required to make an assembly, [create build orders](./build.md) and allocate inventory.
|
||||
|
||||
A part which can be built from other sub components is called an *Assembly*.
|
||||
|
||||
|
|
@ -92,9 +92,16 @@ Note that inherited BOM Line Items only flow "downwards" in the variant inherita
|
|||
!!! info "Editing Inherited Items"
|
||||
When editing an inherited BOM Line Item for a template part, the changes are automatically reflected in the BOM of any variant parts.
|
||||
|
||||
## BOM Creation
|
||||
## BOM Editing
|
||||
|
||||
BOMs can be created manually, by adjusting individual line items, or by uploading (importing) an existing BOM file.
|
||||
Bills of Material (BOMs) can be created manually, by adjusting individual line items, or by uploading (importing) an existing BOM file.
|
||||
|
||||
### Editing Mode
|
||||
|
||||
By default, the BOM is displayed in "view" mode. To edit the BOM, click on the {{ icon("edit", color="blue", title="Edit") }} icon at the top of the BOM panel. This will enable editing mode, which allows you to add, adjust or delete BOM line items.
|
||||
|
||||
!!! warning "Permissions"
|
||||
Only users with the appropriate permissions can edit BOMs. If you do not have permission to edit the BOM, the "Edit" icon will not be visible.
|
||||
|
||||
### Importing a BOM
|
||||
|
||||
|
|
@ -162,89 +169,26 @@ If the BOM requires revalidation, the status will be displayed as "Not Validated
|
|||
|
||||
{{ image("build/bom_invalid.png", "BOM Not Validated") }}
|
||||
|
||||
## Required Quantity Calculation
|
||||
## BOM Comparison
|
||||
|
||||
When a new [Build Order](./build.md) is created, the required production quantity of each component part is calculated based on the BOM line items defined for the assembly being built. To calculate the required production quantity of a component part, the following considerations are made:
|
||||
It is possible to compare the BOM of one assembly with another assembly. This comparison can highlight different component parts, quantities and other properties of the BOM line items.
|
||||
|
||||
### Base Quantity
|
||||
To compare the BOM of one assembly with another, navigate to the "Bill of Materials" tab of the part detail page, then click on the {{ icon("git-compare", color="blue", title="Compare BOM") }} icon at the top of the BOM table:
|
||||
|
||||
The base quantity of a BOM line item is defined by the `Quantity` field of the BOM line item. This is the number of parts which are required to build one assembly. This value is multiplied by the number of assemblies which are being built to determine the total quantity of parts required.
|
||||
{{ image("build/bom_compare_icon.png", "BOM Compare") }}
|
||||
|
||||
```
|
||||
Required Quantity = Base Quantity * Number of Assemblies
|
||||
```
|
||||
This will open the BOM comparison view, which allows you to select a secondary assembly to compare with the primary assembly. The BOM line items of the two assemblies will be displayed side by side, with differences highlighted:
|
||||
|
||||
### Attrition
|
||||
{{ image("build/bom_compare.png", "BOM Compare") }}
|
||||
|
||||
The `Attrition` field of a BOM line item is used to account for expected losses during the production process. This is expressed as a percentage of the `Base Quantity` (e.g. 2%).
|
||||
### Display Mode
|
||||
|
||||
If a non-zero attrition percentage is specified, it is applied to the calculated `Required Quantity` value.
|
||||
When comparing BOMs from two different assemblies, the user can select from the following view modes:
|
||||
|
||||
```
|
||||
Required Quantity = Required Quantity * (1 + Attrition Percentage)
|
||||
```
|
||||
| View Mode | Description |
|
||||
| --- | --- |
|
||||
| *Show all parts* | Display all BOM line items from both assemblies. Differences are highlighted. |
|
||||
| *Show different parts* | Display only the BOM line items which are different between the two assemblies. |
|
||||
| *Show common parts* | Display only the BOM line items which are common between the two assemblies. |
|
||||
|
||||
!!! info "Optional"
|
||||
The attrition percentage is optional. If not specified, it defaults to 0%.
|
||||
|
||||
### Setup Quantity
|
||||
|
||||
The `Setup Quantity` field of a BOM line item is used to account for fixed losses during the production process. This is an additional quantity of the part which is required to ensure that the production run can be completed successfully. This value is added to the calculated `Required Quantity`.
|
||||
|
||||
```
|
||||
Required Quantity = Required Quantity + Setup Quantity
|
||||
```
|
||||
|
||||
!!! info "Optional"
|
||||
The setup quantity is optional. If not specified, it defaults to 0.
|
||||
|
||||
### Rounding Multiple
|
||||
|
||||
The `Rounding Multiple` field of a BOM line item is used to round the calculated `Required Quantity` value to the nearest multiple of the specified value. This is useful for ensuring that the required quantity is a whole number, or to meet specific packaging requirements.
|
||||
|
||||
```
|
||||
Required Quantity = ceil(Required Quantity / Rounding Multiple) * Rounding Multiple
|
||||
```
|
||||
|
||||
!!! info "Optional"
|
||||
The rounding multiple is optional. If not specified, no rounding is applied to the calculated production quantity.
|
||||
|
||||
### Example Calculation
|
||||
|
||||
Consider a BOM line item with the following properties:
|
||||
|
||||
- Base Quantity: 3
|
||||
- Attrition: 2% (0.02)
|
||||
- Setup Quantity: 10
|
||||
- Rounding Multiple: 25
|
||||
|
||||
If we are building 100 assemblies, the required quantity would be calculated as follows:
|
||||
|
||||
```
|
||||
Required Quantity = Base Quantity * Number of Assemblies
|
||||
= 3 * 100
|
||||
= 300
|
||||
|
||||
Attrition Value = Required Quantity * Attrition Percentage
|
||||
= 300 * 0.02
|
||||
= 6
|
||||
|
||||
Required Quantity = Required Quantity + Attrition Value
|
||||
= 300 + 6
|
||||
= 306
|
||||
|
||||
Required Quantity = Required Quantity + Setup Quantity
|
||||
= 306 + 10
|
||||
= 316
|
||||
|
||||
Required Quantity = ceil(Required Quantity / Rounding Multiple) * Rounding Multiple
|
||||
= ceil(316 / 25) * 25
|
||||
= 13 * 25
|
||||
= 325
|
||||
|
||||
```
|
||||
|
||||
So the final required production quantity of the component part would be `325`.
|
||||
|
||||
!!! info "Calculation"
|
||||
The required quantity calculation is performed automatically when a new [Build Order](./build.md) is created.
|
||||
In each case, any differences between the BOM line items are highlighted in red.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
---
|
||||
title: Required Build Quantity
|
||||
---
|
||||
|
||||
## Required Build Quantity
|
||||
|
||||
When a new [Build Order](./build.md) is created, the required production quantity of each component part is calculated based on the BOM line items defined for the assembly being built. To calculate the required production quantity of a component part, the following considerations are made:
|
||||
|
||||
### Base Quantity
|
||||
|
||||
The base quantity of a BOM line item is defined by the `Quantity` field of the BOM line item. This is the number of parts which are required to build one assembly. This value is multiplied by the number of assemblies which are being built to determine the total quantity of parts required.
|
||||
|
||||
```
|
||||
Required Quantity = Base Quantity * Number of Assemblies
|
||||
```
|
||||
|
||||
### Attrition
|
||||
|
||||
The `Attrition` field of a BOM line item is used to account for expected losses during the production process. This is expressed as a percentage of the `Base Quantity` (e.g. 2%).
|
||||
|
||||
If a non-zero attrition percentage is specified, it is applied to the calculated `Required Quantity` value.
|
||||
|
||||
```
|
||||
Required Quantity = Required Quantity * (1 + Attrition Percentage)
|
||||
```
|
||||
|
||||
!!! info "Optional"
|
||||
The attrition percentage is optional. If not specified, it defaults to 0%.
|
||||
|
||||
### Setup Quantity
|
||||
|
||||
The `Setup Quantity` field of a BOM line item is used to account for fixed losses during the production process. This is an additional quantity of the part which is required to ensure that the production run can be completed successfully. This value is added to the calculated `Required Quantity`.
|
||||
|
||||
```
|
||||
Required Quantity = Required Quantity + Setup Quantity
|
||||
```
|
||||
|
||||
!!! info "Optional"
|
||||
The setup quantity is optional. If not specified, it defaults to 0.
|
||||
|
||||
### Rounding Multiple
|
||||
|
||||
The `Rounding Multiple` field of a BOM line item is used to round the calculated `Required Quantity` value to the nearest multiple of the specified value. This is useful for ensuring that the required quantity is a whole number, or to meet specific packaging requirements.
|
||||
|
||||
```
|
||||
Required Quantity = ceil(Required Quantity / Rounding Multiple) * Rounding Multiple
|
||||
```
|
||||
|
||||
!!! info "Optional"
|
||||
The rounding multiple is optional. If not specified, no rounding is applied to the calculated production quantity.
|
||||
|
||||
### Example Calculation
|
||||
|
||||
Consider a BOM line item with the following properties:
|
||||
|
||||
- Base Quantity: 3
|
||||
- Attrition: 2% (0.02)
|
||||
- Setup Quantity: 10
|
||||
- Rounding Multiple: 25
|
||||
|
||||
If we are building 100 assemblies, the required quantity would be calculated as follows:
|
||||
|
||||
```
|
||||
Required Quantity = Base Quantity * Number of Assemblies
|
||||
= 3 * 100
|
||||
= 300
|
||||
|
||||
Attrition Value = Required Quantity * Attrition Percentage
|
||||
= 300 * 0.02
|
||||
= 6
|
||||
|
||||
Required Quantity = Required Quantity + Attrition Value
|
||||
= 300 + 6
|
||||
= 306
|
||||
|
||||
Required Quantity = Required Quantity + Setup Quantity
|
||||
= 306 + 10
|
||||
= 316
|
||||
|
||||
Required Quantity = ceil(Required Quantity / Rounding Multiple) * Rounding Multiple
|
||||
= ceil(316 / 25) * 25
|
||||
= 13 * 25
|
||||
= 325
|
||||
|
||||
```
|
||||
|
||||
So the final required production quantity of the component part would be `325`.
|
||||
|
||||
!!! info "Calculation"
|
||||
The required quantity calculation is performed automatically when a new [Build Order](./build.md) is created.
|
||||
|
|
@ -10,6 +10,9 @@ Plugins can inherit from the [UserInterfaceMixin](./mixins/ui.md) class to provi
|
|||
|
||||
Note that the [InvenTree plugin creator](./creator.md) can be used to scaffold a new plugin with the necessary structure for frontend integration. This will automatically set up the necessary files and configurations to get started with frontend development.
|
||||
|
||||
!!! info "Simplified Development"
|
||||
Using the plugin creator can significantly simplify the process of developing a frontend plugin, as it provides a ready-made template with the necessary configurations for building and integrating the frontend code. Rolling your own frontend plugin from scratch is possible, but it requires a good understanding of the InvenTree frontend architecture and build process.
|
||||
|
||||
## Frontend Architecture
|
||||
|
||||
When designing a frontend plugin component, it is important to have at least a basic understanding of the InvenTree frontend architecture.
|
||||
|
|
@ -76,20 +79,28 @@ The following properties are available in the `context` object:
|
|||
| Property | Description |
|
||||
| -------- | ----------- |
|
||||
| `version` | An object containing the current InvenTree version information. |
|
||||
| `api` | The Axios instance configured to communicate with the InvenTree API. |
|
||||
| `queryClient` | The query client instance used for managing API calls in the frontend. |
|
||||
| `user` | An object containing information about the currently logged-in user. |
|
||||
| `userSettings` | An object containing user-specific settings. |
|
||||
| `globalSettings` | An object containing global settings for the InvenTree instance. |
|
||||
| `modelInformation` | An object containing information about the models available in the InvenTree instance. |
|
||||
| `renderInstance` | A function to render a model instance |
|
||||
| `host` | An object containing information about the current host (server) configuration. |
|
||||
| `i18n` | An object containing internationalization (i18n) functions for translating text. |
|
||||
| `locale` | The current locale being used for the user interface. |
|
||||
| `api` | The Axios instance configured to communicate with the InvenTree API. |
|
||||
| `queryClient` | The query client instance used for managing API calls in the frontend. |
|
||||
| `navigate` | A function to navigate to a different page in the InvenTree web interface. |
|
||||
| `globalSettings` | An object containing global settings for the InvenTree instance. |
|
||||
| `userSettings` | An object containing user-specific settings. |
|
||||
| `modelInformation` | An object containing information about the models available in the InvenTree instance. |
|
||||
| `renderInstance` | A function to render a model instance |
|
||||
| `theme` | The current Mantine theme being used in the InvenTree web interface. |
|
||||
| `colorScheme` | The current color scheme being used in the InvenTree web interface. |
|
||||
| `forms` | A set of functional components for rendering forms in the InvenTree web interface. |
|
||||
| `tables` | A set of functional components for rendering tables in the InvenTree web interface. |
|
||||
| `importer` | A set of functions for controlling the global importer drawer in the InvenTree web interface. |
|
||||
| `model` | The model type associated with the rendered component (if applicable). |
|
||||
| `id` | The ID (primary key) of the model instance being rendered (if applicable). |
|
||||
| `instance` | The model instance data (if available). |
|
||||
| `reloadContent` | A function which can be called to reload the plugin content. |
|
||||
| `reloadInstance` | A function which can be called to reload the model instance. |
|
||||
| `context` | Any additional context data which may be passed to the plugin. |
|
||||
|
||||
This set of components is passed through at render time to the plugin function, allowing the plugin code to hook directly into the InvenTree web interface and access the necessary context for rendering.
|
||||
|
||||
|
|
|
|||
|
|
@ -8,3 +8,27 @@ If this mixin is added to a plugin the directory the plugin class is defined in
|
|||
|
||||
!!! warning "Danger Zone"
|
||||
Only use this mixin if you have an understanding of Django's [app system]({% include "django.html" %}/ref/applications). Plugins with this mixin are deeply integrated into InvenTree and can cause difficult to reproduce or long-running errors. Use the built-in testing functions of Django to make sure your code does not cause unwanted behaviour in InvenTree before releasing.
|
||||
|
||||
## Custom Models
|
||||
|
||||
This mixin allows you to define custom database models within your plugin. These models will be automatically registered with the InvenTree server, and will be available for use within your plugin code.
|
||||
|
||||
### Model Permissions
|
||||
|
||||
Some database operations within the InvenTree ecosystem may require custom permissions checks - to determine which actions a user can perform against a given model. If your plugin defines custom models, you may need to implement a custom permission check method on your model class.
|
||||
|
||||
Each model class can implement a `check_user_permission` classmethod, which will be called by the InvenTree permission system when checking permissions for that model. This method should return `True` if the user has the required permissions, and `False` otherwise.
|
||||
|
||||
|
||||
```python
|
||||
class MyCustomModel(models.Model):
|
||||
# model fields here
|
||||
|
||||
@classmethod
|
||||
def check_user_permission(cls, user: User, permission: str) -> bool:
|
||||
# custom permission logic here
|
||||
return True # or False
|
||||
```
|
||||
|
||||
!!! warning "Default Permissions"
|
||||
By default, if the `check_user_permission` method is not implemented, the InvenTree permission system will return `False` for all permission checks against that model. This is to ensure that no permissions are granted by default, and that the plugin developer must explicitly define the required permissions for their custom models.
|
||||
|
|
|
|||
|
|
@ -9,11 +9,10 @@ For complicated plugins it makes sense to add unit tests the code to ensure that
|
|||
|
||||
For plugin testing the following environment variables must be set to True:
|
||||
|
||||
| Name | Function | Value |
|
||||
| ---- | -------- | ----- |
|
||||
| INVENTREE_PLUGINS_ENABLED | Enables the use of 3rd party plugins | True |
|
||||
| INVENTREE_PLUGIN_TESTING | Enables enables all plugins no matter of their active state in the db or built-in flag | True |
|
||||
| INVENTREE_PLUGIN_TESTING_SETUP | Enables the url mixin | True |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_PLUGINS_ENABLED") }} Enables the use of 3rd party plugins |
|
||||
{{ configsetting("INVENTREE_PLUGIN_TESTING") }} Enables enables all plugins no matter of their active state in the db or built-in flag |
|
||||
{{ configsetting("INVENTREE_PLUGIN_TESTING_SETUP") }} Enables the url mixin |
|
||||
|
||||
### Test Program
|
||||
|
||||
|
|
|
|||
|
|
@ -29,10 +29,10 @@ The first step is to ensure that the required provider modules are installed, vi
|
|||
|
||||
There are two variables in the configuration file which define the operation of SSO:
|
||||
|
||||
| Environment Variable |Configuration File | Description | More Info |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_SOCIAL_BACKENDS | `social_backends` | A *list* of provider backends enabled for the InvenTree instance | [django-allauth docs](https://docs.allauth.org/en/latest/installation/quickstart.html) |
|
||||
| INVENTREE_SOCIAL_PROVIDERS | `social_providers` | A *dict* of settings specific to the installed providers | [provider documentation](https://docs.allauth.org/en/latest/socialaccount/providers/index.html) |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_SOCIAL_BACKENDS") }} A *list* of [social provider backends](https://docs.allauth.org/en/latest/installation/quickstart.html) enabled for the InvenTree instance |
|
||||
{{ configsetting("INVENTREE_SOCIAL_PROVIDERS") }} A *dict* of settings specific to the [installed providers](https://docs.allauth.org/en/latest/socialaccount/providers/index.html) |
|
||||
|
||||
|
||||
In the example below, SSO provider modules are activated for *google*, *github* and *microsoft*. Specific configuration options are specified for the *microsoft* provider module:
|
||||
|
||||
|
|
|
|||
|
|
@ -105,6 +105,11 @@ A potentially critical mismatch between the backup environment and the current r
|
|||
|
||||
While using [invoke](../start/invoke.md), this can be overridden with the `--restore-allow-newer-version` flag.
|
||||
|
||||
#### INVE-E17
|
||||
**Error rendering component**
|
||||
|
||||
An error occurred while rendering a component in the frontend. Typically this is caused by a browser caching issue, and can be resolved by clearing the browser cache and refreshing the page. If the issue persists, check the browser console for more information about the error.
|
||||
|
||||
### INVE-W (InvenTree Warning)
|
||||
Warnings - These are non-critical errors which should be addressed when possible.
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ InvenTree functionality is split into a number of distinct roles. A group will h
|
|||
| Role | Description |
|
||||
| ---- | ----------- |
|
||||
| **Admin** | The *admin* role is related to assigning user permissions. |
|
||||
| **Build** | The *build* role is related to accessing Build Order and Bill of Materials data |
|
||||
| **BOM** | The *bom* role is related to accessing Bill of Materials data |
|
||||
| **Build** | The *build* role is related to accessing manufacturing / Build Order |
|
||||
| **Part** | The *part* role is related to accessing Part data |
|
||||
| **Part Category** | The *part category* role is related to accessing Part Category data |
|
||||
| **Purchase Order** | The *purchase* role is related to accessing Purchase Order data |
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ The *Display Settings* screen shows general display configuration options:
|
|||
{{ usersetting("FORMS_CLOSE_USING_ESCAPE") }}
|
||||
{{ usersetting("DISPLAY_STOCKTAKE_TAB") }}
|
||||
{{ usersetting("SHOW_FULL_CATEGORY_IN_TABLES")}}
|
||||
{{ usersetting("SHOW_BOM_SUBASSEMBLY_LEVELS")}}
|
||||
{{ usersetting("ENABLE_LAST_BREADCRUMB") }}
|
||||
{{ usersetting("SHOW_FULL_LOCATION_IN_TABLES") }}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,46 +51,44 @@ You can link your InvenTree server to an LDAP server.
|
|||
|
||||
Next you can start configuring the connection. Either use the config file or set the environment variables.
|
||||
|
||||
| config key | ENV Variable | Description |
|
||||
| --- | --- | --- |
|
||||
| `ldap.enabled` | `INVENTREE_LDAP_ENABLED` | Set this to `True` to enable LDAP. |
|
||||
| `ldap.debug` | `INVENTREE_LDAP_DEBUG` | Set this to `True` to activate debug mode, useful for troubleshooting ldap configurations. |
|
||||
| `ldap.server_uri` | `INVENTREE_LDAP_SERVER_URI` | LDAP Server URI, e.g. `ldaps://example.org` |
|
||||
| `ldap.start_tls` | `INVENTREE_LDAP_START_TLS` | Enable TLS encryption over the standard LDAP port, [see](https://django-auth-ldap.readthedocs.io/en/latest/reference.html#auth-ldap-start-tls). (You can set TLS options via `ldap.global_options`) |
|
||||
| `ldap.bind_dn` | `INVENTREE_LDAP_BIND_DN` | LDAP bind dn, e.g. `cn=admin,dc=example,dc=org` |
|
||||
| `ldap.bind_password` | `INVENTREE_LDAP_BIND_PASSWORD` | LDAP bind password |
|
||||
| `ldap.search_base_dn` | `INVENTREE_LDAP_SEARCH_BASE_DN` | LDAP search base dn, e.g. `cn=Users,dc=example,dc=org` |
|
||||
| `ldap.user_dn_template` | `INVENTREE_LDAP_USER_DN_TEMPLATE` | use direct bind as auth user, `ldap.bind_dn` and `ldap.bin_password` is not necessary then, e.g. `uid=%(user)s,dc=example,dc=org` |
|
||||
| `ldap.global_options` | `INVENTREE_LDAP_GLOBAL_OPTIONS` | set advanced options as dict, e.g. TLS settings. For a list of all available options, see [python-ldap docs](https://www.python-ldap.org/en/latest/reference/ldap.html#ldap-options). (keys and values starting with OPT_ get automatically converted to `python-ldap` keys) |
|
||||
| `ldap.search_filter_str`| `INVENTREE_LDAP_SEARCH_FILTER_STR` | LDAP search filter str, default: `uid=%(user)s` |
|
||||
| `ldap.user_attr_map` | `INVENTREE_LDAP_USER_ATTR_MAP` | LDAP <-> InvenTree user attribute map, can be json if used as env, in yml directly specify the object. default: `{"first_name": "givenName", "last_name": "sn", "email": "mail"}` |
|
||||
| `ldap.always_update_user` | `INVENTREE_LDAP_ALWAYS_UPDATE_USER` | Always update the user on each login, default: `true` |
|
||||
| `ldap.cache_timeout` | `INVENTREE_LDAP_CACHE_TIMEOUT` | cache timeout to reduce traffic with LDAP server, default: `3600` (1h) |
|
||||
| `ldap.group_search` | `INVENTREE_LDAP_GROUP_SEARCH` | Base LDAP DN for group searching; required to enable group features |
|
||||
| `ldap.group_object_class` | `INVENTREE_LDAP_GROUP_OBJECT_CLASS` | The string to pass to the LDAP group search `(objectClass=<...>)`, default: `groupOfUniqueNames` |
|
||||
| `ldap.mirror_groups` | `INVENTREE_LDAP_MIRROR_GROUPS` | If `True`, mirror a user's LDAP group membership in the Django database, default: `False` |
|
||||
| `ldap.group_type_class` | `INVENTREE_LDAP_GROUP_TYPE_CLASS` | The group class to be imported from `django_auth_ldap.config` as a string, default: `'GroupOfUniqueNamesType'`|
|
||||
| `ldap.group_type_class_args` | `INVENTREE_LDAP_GROUP_TYPE_CLASS_ARGS` | A `list` of positional args to pass to the LDAP group type class, default `[]` |
|
||||
| `ldap.group_type_class_kwargs` | `INVENTREE_LDAP_GROUP_TYPE_CLASS_KWARGS` | A `dict` of keyword args to pass to the LDAP group type class, default `{'name_attr': 'cn'}` |
|
||||
| `ldap.require_group` | `INVENTREE_LDAP_REQUIRE_GROUP` | If set, users _must_ be in this group to log in to InvenTree |
|
||||
| `ldap.deny_group` | `INVENTREE_LDAP_DENY_GROUP` | If set, users _must not_ be in this group to log in to InvenTree |
|
||||
| `ldap.user_flags_by_group` | `INVENTREE_LDAP_USER_FLAGS_BY_GROUP` | LDAP group to InvenTree user flag map, can be json if used as env, in yml directly specify the object. See config template for example, default: `{}` |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_LDAP_ENABLED") }} Enable LDAP support |
|
||||
{{ configsetting("INVENTREE_LDAP_DEBUG") }} Set this to `True` to activate debug mode, useful for troubleshooting ldap configurations. |
|
||||
| `INVENTREE_LDAP_SERVER_URI` | `ldap.server_uri` | LDAP Server URI, e.g. `ldaps://example.org` |
|
||||
| `INVENTREE_LDAP_START_TLS` | `ldap.start_tls` | Enable TLS encryption over the standard LDAP port, [see](https://django-auth-ldap.readthedocs.io/en/latest/reference.html#auth-ldap-start-tls). (You can set TLS options via `ldap.global_options`) |
|
||||
| `INVENTREE_LDAP_BIND_DN` | `ldap.bind_dn` | LDAP bind dn, e.g. `cn=admin,dc=example,dc=org` |
|
||||
| `INVENTREE_LDAP_BIND_PASSWORD` | `ldap.bind_password` | LDAP bind password |
|
||||
| `INVENTREE_LDAP_SEARCH_BASE_DN` | `ldap.search_base_dn` | LDAP search base dn, e.g. `cn=Users,dc=example,dc=org` |
|
||||
| `INVENTREE_LDAP_USER_DN_TEMPLATE` | `ldap.user_dn_template` | use direct bind as auth user, `ldap.bind_dn` and `ldap.bin_password` is not necessary then, e.g. `uid=%(user)s,dc=example,dc=org` |
|
||||
| `INVENTREE_LDAP_GLOBAL_OPTIONS` | `ldap.global_options` | set advanced options as dict, e.g. TLS settings. For a list of all available options, see [python-ldap docs](https://www.python-ldap.org/en/latest/reference/ldap.html#ldap-options). (keys and values starting with OPT_ get automatically converted to `python-ldap` keys) |
|
||||
| `INVENTREE_LDAP_SEARCH_FILTER_STR`| `ldap.search_filter_str` | LDAP search filter str, default: `uid=%(user)s` |
|
||||
| `INVENTREE_LDAP_USER_ATTR_MAP` | `ldap.user_attr_map` | LDAP <-> InvenTree user attribute map, can be json if used as env, in yml directly specify the object. default: `{"first_name": "givenName", "last_name": "sn", "email": "mail"}` |
|
||||
| `INVENTREE_LDAP_ALWAYS_UPDATE_USER` | `ldap.always_update_user` | Always update the user on each login, default: `true` |
|
||||
| `INVENTREE_LDAP_CACHE_TIMEOUT` | `ldap.cache_timeout` | cache timeout to reduce traffic with LDAP server, default: `3600` (1h) |
|
||||
| `INVENTREE_LDAP_GROUP_SEARCH` | `ldap.group_search` | Base LDAP DN for group searching; required to enable group features |
|
||||
| `INVENTREE_LDAP_GROUP_OBJECT_CLASS` | `ldap.group_object_class` | The string to pass to the LDAP group search `(objectClass=<...>)`, default: `groupOfUniqueNames` |
|
||||
| `INVENTREE_LDAP_MIRROR_GROUPS` | `ldap.mirror_groups` | If `True`, mirror a user's LDAP group membership in the Django database, default: `False` |
|
||||
| `INVENTREE_LDAP_GROUP_TYPE_CLASS` | `ldap.group_type_class` | The group class to be imported from `django_auth_ldap.config` as a string, default: `'GroupOfUniqueNamesType'`|
|
||||
| `INVENTREE_LDAP_GROUP_TYPE_CLASS_ARGS` | `ldap.group_type_class_args` | A `list` of positional args to pass to the LDAP group type class, default `[]` |
|
||||
| `INVENTREE_LDAP_GROUP_TYPE_CLASS_KWARGS` | `ldap.group_type_class_kwargs` | A `dict` of keyword args to pass to the LDAP group type class, default `{'name_attr': 'cn'}` |
|
||||
| `INVENTREE_LDAP_REQUIRE_GROUP` | `ldap.require_group` | If set, users _must_ be in this group to log in to InvenTree |
|
||||
| `INVENTREE_LDAP_DENY_GROUP` | `ldap.deny_group` | If set, users _must not_ be in this group to log in to InvenTree |
|
||||
| `INVENTREE_LDAP_USER_FLAGS_BY_GROUP` | `ldap.user_flags_by_group` | LDAP group to InvenTree user flag map, can be json if used as env, in yml directly specify the object. See config template for example, default: `{}` |
|
||||
|
||||
## Tracing support
|
||||
|
||||
Starting with 0.14.0 InvenTree supports sending traces, logs and metrics to OpenTelemetry compatible endpoints (both HTTP and gRPC). A [list of vendors](https://opentelemetry.io/ecosystem/vendors) is available on the project site.
|
||||
This can be used to track usage and performance of the InvenTree backend and connected services like databases, caches and more.
|
||||
|
||||
| config key | ENV Variable | Description |
|
||||
| --- | --- | --- |
|
||||
| `tracing.enabled` | `INVENTREE_TRACING_ENABLED` | Set this to `True` to enable OpenTelemetry. |
|
||||
| `tracing.endpoint` | `INVENTREE_TRACING_ENDPOINT` | General endpoint for information (not specific trace/log url) |
|
||||
| `tracing.headers` | `INVENTREE_TRACING_HEADERS` | HTTP headers that should be send with every request (often used for authentication). Format as a dict. |
|
||||
| `tracing.auth.basic` | `INVENTREE_TRACING_AUTH_BASIC` | Auth headers that should be send with every requests (will be encoded to b64 and overwrite auth headers) |
|
||||
| `tracing.is_http` | `INVENTREE_TRACING_IS_HTTP` | Are the endpoints HTTP (True, default) or gRPC (False) |
|
||||
| `tracing.append_http` | `INVENTREE_TRACING_APPEND_HTTP` | Append default url routes (v1) to `tracing.endpoint` |
|
||||
| `tracing.console` | `INVENTREE_TRACING_CONSOLE` | Print out all exports (additionally) to the console for debugging. Do not use in production |
|
||||
| `tracing.resources` | `INVENTREE_TRACING_RESOURCES` | Add additional resources to all exports. This can be used to add custom tags to the traces. Format as a dict. |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_TRACING_ENABLED") }} Enable OpenTelemetry |
|
||||
{{ configsetting("INVENTREE_TRACING_ENDPOINT") }} General endpoint for information (not specific trace/log url) |
|
||||
{{ configsetting("INVENTREE_TRACING_HEADERS") }} HTTP headers that should be send with every request (often used for authentication). Format as a dict. |
|
||||
{{ configsetting("INVENTREE_TRACING_AUTH") }} Auth headers that should be send with every requests (will be encoded to b64 and overwrite auth headers) |
|
||||
{{ configsetting("INVENTREE_TRACING_IS_HTTP") }} Are the endpoints HTTP (True, default) or gRPC (False) |
|
||||
{{ configsetting("INVENTREE_TRACING_APPEND_HTTP") }} Append default url routes (v1) to `tracing.endpoint` |
|
||||
{{ configsetting("INVENTREE_TRACING_CONSOLE") }} Print out all exports (additionally) to the console for debugging. Do not use in production |
|
||||
{{ configsetting("INVENTREE_TRACING_RESOURCES") }} Add additional resources to all exports. This can be used to add custom tags to the traces. Format as a dict. |
|
||||
|
||||
## Multi Site Support
|
||||
|
||||
|
|
@ -99,7 +97,6 @@ If your InvenTree instance is used in a multi-site environment, you can enable m
|
|||
!!! tip "Django Documentation"
|
||||
For more information on multi-site support, refer to the [Django documentation]({% include "django.html" %}/ref/contrib/sites/).
|
||||
|
||||
| Environment Variable | Config Key | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_SITE_MULTI | site_multi | Enable multiple sites | False |
|
||||
| INVENTREE_SITE_ID | site_id | Specify a fixed site ID | _Not specified_ |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_SITE_MULTI") }} Enable multiple sites |
|
||||
{{ configsetting("INVENTREE_SITE_ID") }} Specify a fixed site ID |
|
||||
|
|
|
|||
|
|
@ -17,19 +17,19 @@ The django-dbbackup library provides [multiple configuration options](https://ar
|
|||
|
||||
The following configuration options are available for backup:
|
||||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_BACKUP_STORAGE | backup_storage | Backup storage backend. Refer to the [storage backend documentation](#storage-backend). | django.core.files.storage.FileSystemStorage |
|
||||
| INVENTREE_BACKUP_DIR | backup_dir | Backup storage directory. | *No default* |
|
||||
| INVENTREE_BACKUP_OPTIONS | backup_options | Specific options for the selected storage backend (dict) | *No default* |
|
||||
| INVENTREE_BACKUP_CONNECTOR_OPTIONS | backup_connector_options | Specific options for the database connector (dict). Refer to the [database connector options](#database-connector). | *No default* |
|
||||
| INVENTREE_BACKUP_SEND_EMAIL | backup_send_email | If True, an email is sent to the site admin when an error occurs during a backup or restore procedure. | False |
|
||||
| INVENTREE_BACKUP_EMAIL_PREFIX | backup_email_prefix | Prefix for the subject line of backup-related emails. | `[InvenTree Backup]` |
|
||||
| INVENTREE_BACKUP_GPG_RECIPIENT | backup_gpg_recipient | Specify GPG recipient if using encryption for backups. | *No default* |
|
||||
| INVENTREE_BACKUP_DATE_FORMAT | backup_date_format | Date format string used to format timestamps in backup filenames. | `%Y-%m-%d-%H%M%S` |
|
||||
| INVENTREE_BACKUP_DATABASE_FILENAME_TEMPLATE | backup_database_filename_template | Template string used to generate database backup filenames. | `InvenTree-db-{datetime}.{extension}` |
|
||||
| INVENTREE_BACKUP_MEDIA_FILENAME_TEMPLATE | backup_media_filename_template | Template string used to generate media backup filenames. | `InvenTree-media-{datetime}.{extension}` |
|
||||
| INVENTREE_BACKUP_RESTORE_ALLOW_NEWER_VERSION | backup_restore_allow_newer_version | If True, allows restoring a backup created with a newer version of InvenTree. This is dangerous as it can lead to hard-to-debug data loss. | False |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_BACKUP_STORAGE") }} Backup storage backend. Refer to the [storage backend documentation](#storage-backend). |
|
||||
{{ configsetting("INVENTREE_BACKUP_DIR") }} Backup storage directory. |
|
||||
{{ configsetting("INVENTREE_BACKUP_OPTIONS") }} Specific options for the selected storage backend (dict) |
|
||||
{{ configsetting("INVENTREE_BACKUP_CONNECTOR_OPTIONS") }} Specific options for the database connector (dict). Refer to the [database connector options](#database-connector) |
|
||||
{{ configsetting("INVENTREE_BACKUP_SEND_EMAIL") }} If True, an email is sent to the site admin when an error occurs during a backup or restore procedure. |
|
||||
{{ configsetting("INVENTREE_BACKUP_EMAIL_PREFIX") }} Prefix for the subject line of backup-related emails. |
|
||||
{{ configsetting("INVENTREE_BACKUP_GPG_RECIPIENT") }} Specify GPG recipient if using encryption for backups. |
|
||||
{{ configsetting("INVENTREE_BACKUP_DATE_FORMAT") }} Date format string used to format timestamps in backup filenames. |
|
||||
{{ configsetting("INVENTREE_BACKUP_DATABASE_FILENAME_TEMPLATE") }} Template string used to generate database backup filenames.
|
||||
{{ configsetting("INVENTREE_BACKUP_MEDIA_FILENAME_TEMPLATE") }} Template string used to generate media backup filenames. |
|
||||
{{ configsetting("INVENTREE_BACKUP_RESTORE_ALLOW_NEWER_VERSION") }} If True, allows restoring a backup created with a newer version of InvenTree. This is dangerous as it can lead to hard-to-debug data loss. |
|
||||
|
||||
|
||||
### Storage Backend
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ Environment variable settings generally use the `INVENTREE_` prefix, and are all
|
|||
!!! warning "Available Variables"
|
||||
Some configuration options cannot be set via environment variables. Refer to the documentation below.
|
||||
|
||||
!!! info "Environment Variable Priority"
|
||||
Note that a provided environment variable will override the value provided in the configuration file.
|
||||
|
||||
#### List Values
|
||||
|
||||
To specify a list value in an environment variable, use a comma-separated list. For example, to specify a list of trusted origins:
|
||||
|
|
@ -58,14 +61,13 @@ INVENTREE_TRUSTED_ORIGINS='https://inventree.example.com:8443,https://stock.exam
|
|||
|
||||
The following basic options are available:
|
||||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_SITE_URL | site_url | Specify a fixed site URL | *Not specified* |
|
||||
| INVENTREE_TIMEZONE | timezone | Server timezone | UTC |
|
||||
| INVENTREE_ADMIN_ENABLED | admin_enabled | Enable the [django administrator interface]({% include "django.html" %}/ref/contrib/admin/) | True |
|
||||
| INVENTREE_ADMIN_URL | admin_url | URL for accessing [admin interface](../settings/admin.md) | admin |
|
||||
| INVENTREE_LANGUAGE | language | Default language | en-us |
|
||||
| INVENTREE_AUTO_UPDATE | auto_update | Database migrations will be run automatically | False |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_SITE_URL") }} Specify a fixed site URL |
|
||||
{{ configsetting("INVENTREE_TIMEZONE") }} Server timezone |
|
||||
{{ configsetting("INVENTREE_ADMIN_ENABLED") }} Enable the [django administrator interface]({% include "django.html" %}/ref/contrib/admin/) |
|
||||
{{ configsetting("INVENTREE_ADMIN_URL") }} URL for accessing [admin interface](../settings/admin.md) |
|
||||
{{ configsetting("INVENTREE_LANGUAGE") }} Default language |
|
||||
{{ configsetting("INVENTREE_AUTO_UPDATE") }} Database migrations will be run automatically |
|
||||
|
||||
### Site URL
|
||||
|
||||
|
|
@ -89,18 +91,17 @@ With "auto update" enabled, the InvenTree server will automatically apply databa
|
|||
|
||||
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_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 |
|
||||
| INVENTREE_WRITE_LOG | write_log | Enable writing of log messages to file at config base | False |
|
||||
| INVENTREE_CONSOLE_LOG | console_log | Enable logging to console | True |
|
||||
| INVENTREE_SCHEMA_LEVEL | schema.level | Set level of added schema extensions detail (0-3) 0 = including no additional detail | 0 |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_DEBUG") }} Enable [debug mode](./index.md#debug-mode) |
|
||||
{{ configsetting("INVENTREE_DB_LOGGING") }} Enable logging of database messages |
|
||||
{{ configsetting("INVENTREE_LOG_LEVEL") }} Set level of logging to terminal |
|
||||
{{ configsetting("INVENTREE_JSON_LOG") }} Log messages as json |
|
||||
{{ configsetting("INVENTREE_WRITE_LOG") }} Enable writing of log messages to file at config base |
|
||||
{{ configsetting("INVENTREE_CONSOLE_LOG") }} Enable logging to console |
|
||||
{{ configsetting("INVENTREE_SCHEMA_LEVEL") }} Set level of added schema extensions detail (0-3) 0 = including no additional detail |
|
||||
{{ configsetting("INVENTREE_DEBUG_QUERYCOUNT") }} Enable support for [django-querycount](../develop/index.md#django-querycount) middleware. |
|
||||
{{ configsetting("INVENTREE_DEBUG_SILK") }} Enable support for [django-silk](../develop/index.md#django-silk) profiling tool. |
|
||||
| `INVENTREE_DEBUG_SILK_PROFILING` | `debug_silk_profiling` | False | Enable detailed profiling in django-silk |
|
||||
|
||||
### Debug Mode
|
||||
|
||||
|
|
@ -118,26 +119,24 @@ Depending on how your InvenTree installation is configured, you will need to pay
|
|||
!!! danger "Not Secure"
|
||||
Allowing access from any host is not secure, and should be adjusted for your installation.
|
||||
|
||||
!!! info "Environment Variables"
|
||||
Note that a provided environment variable will override the value provided in the configuration file.
|
||||
|
||||
!!! success "INVENTREE_SITE_URL"
|
||||
If you have specified the `INVENTREE_SITE_URL`, this will automatically be used as a trusted CSRF and CORS host (see below).
|
||||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_ALLOWED_HOSTS | allowed_hosts | List of allowed hosts | `*` |
|
||||
| INVENTREE_TRUSTED_ORIGINS | trusted_origins | List of trusted origins. Refer to the [django documentation]({% include "django.html" %}/ref/settings/#csrf-trusted-origins) | Uses the *INVENTREE_SITE_URL* parameter, if set. Otherwise, an empty list. |
|
||||
| INVENTREE_CORS_ORIGIN_ALLOW_ALL | cors.allow_all | Allow all remote URLS for CORS checks | `False` |
|
||||
| INVENTREE_CORS_ORIGIN_WHITELIST | cors.whitelist | List of whitelisted CORS URLs. Refer to the [django-cors-headers documentation](https://github.com/adamchainz/django-cors-headers#cors_allowed_origins-sequencestr) | Uses the *INVENTREE_SITE_URL* parameter, if set. Otherwise, an empty list. |
|
||||
| INVENTREE_CORS_ORIGIN_REGEX | cors.regex | List of regular expressions for CORS whitelisted URL patterns | *Empty list* |
|
||||
| INVENTREE_CORS_ALLOW_CREDENTIALS | cors.allow_credentials | Allow cookies in cross-site requests | `True` |
|
||||
| INVENTREE_SITE_LAX_PROTOCOL | site_lax_protocol | Ignore protocol mismatches on INVE-E7 site checks | `True` |
|
||||
| INVENTREE_USE_X_FORWARDED_HOST | use_x_forwarded_host | Use forwarded host header | `False` |
|
||||
| INVENTREE_USE_X_FORWARDED_PORT | use_x_forwarded_port | Use forwarded port header | `False` |
|
||||
| INVENTREE_USE_X_FORWARDED_PROTO | use_x_forwarded_proto | Use forwarded protocol header | `False` |
|
||||
| INVENTREE_SESSION_COOKIE_SECURE | cookie.secure | Enforce secure session cookies | `False` |
|
||||
| INVENTREE_COOKIE_SAMESITE | cookie.samesite | Session cookie mode. Must be one of `Strict | Lax | None | False`. Refer to the [mozilla developer docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) and the [django documentation]({% include "django.html" %}/ref/settings/#std-setting-SESSION_COOKIE_SAMESITE) for more information. | False |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_ALLOWED_HOSTS") }} List of allowed hosts |
|
||||
{{ configsetting("INVENTREE_TRUSTED_ORIGINS", default="Uses the *INVENTREE_SITE_URL* parameter, if set. Otherwise, an empty list.") }} List of trusted origins. Refer to the [django documentation]({% include "django.html" %}/ref/settings/#csrf-trusted-origins) |
|
||||
{{ configsetting("INVENTREE_CORS_ORIGIN_ALLOW_ALL") }} Allow all remote URLS for CORS checks |
|
||||
{{ configsetting("INVENTREE_CORS_ORIGIN_WHITELIST", default="Uses the *INVENTREE_SITE_URL* parameter, if set. Otherwise, an empty list.") }} List of whitelisted CORS URLs. Refer to the [django-cors-headers documentation](https://github.com/adamchainz/django-cors-headers#cors_allowed_origins-sequencestr) |
|
||||
{{ configsetting("INVENTREE_CORS_ORIGIN_REGEX") }} List of regular expressions for CORS whitelisted URL patterns |
|
||||
{{ configsetting("INVENTREE_CORS_ALLOW_CREDENTIALS") }} Allow cookies in cross-site requests |
|
||||
{{ configsetting("INVENTREE_SITE_LAX_PROTOCOL") }} Ignore protocol mismatches on INVE-E7 site checks |
|
||||
{{ configsetting("INVENTREE_USE_X_FORWARDED_HOST") }} Use forwarded host header |
|
||||
{{ configsetting("INVENTREE_USE_X_FORWARDED_PORT") }} Use forwarded port header |
|
||||
{{ configsetting("INVENTREE_USE_X_FORWARDED_PROTO") }} Use forwarded protocol header |
|
||||
| `INVENTREE_X_FORWARDED_PROTO_NAME` | `x_forwarded_proto_name` | `HTTP_X_FORWARDED_PROTO` | Name of the header to use for forwarded protocol information |
|
||||
{{ configsetting("INVENTREE_SESSION_COOKIE_SECURE") }} Enforce secure session cookies |
|
||||
{{ configsetting("INVENTREE_COOKIE_SAMESITE") }} Session cookie mode. Must be one of `Strict | Lax | None | False`. Refer to the [mozilla developer docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) and the [django documentation]({% include "django.html" %}/ref/settings/#std-setting-SESSION_COOKIE_SAMESITE) for more information. |
|
||||
|
||||
|
||||
### Debug Mode
|
||||
|
||||
|
|
@ -191,10 +190,9 @@ Django provides a powerful [administrator interface]({% include "django.html" %}
|
|||
|
||||
The following admin site configuration options are available:
|
||||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_ADMIN_ENABLED | admin_enabled | Enable the django administrator interface | True |
|
||||
| INVENTREE_ADMIN_URL | admin_url | URL for accessing the admin interface | admin |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_ADMIN_ENABLED") }} Enable the django administrator interface |
|
||||
{{ configsetting("INVENTREE_ADMIN_URL") }} URL for accessing the admin interface |
|
||||
|
||||
!!! warning "Security"
|
||||
Changing the admin URL is a simple way to improve security, but it is not a substitute for proper security practices.
|
||||
|
|
@ -203,12 +201,11 @@ The following admin site configuration options are available:
|
|||
|
||||
An administrator account can be specified using the following environment variables:
|
||||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_ADMIN_USER | admin_user | Admin account username | *Not specified* |
|
||||
| INVENTREE_ADMIN_PASSWORD | admin_password | Admin account password | *Not specified* |
|
||||
| INVENTREE_ADMIN_PASSWORD_FILE | admin_password_file | Admin account password file | *Not specified* |
|
||||
| INVENTREE_ADMIN_EMAIL | admin_email |Admin account email address | *Not specified* |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_ADMIN_USER") }} Admin account username |
|
||||
{{ configsetting("INVENTREE_ADMIN_PASSWORD") }} Admin account password |
|
||||
{{ configsetting("INVENTREE_ADMIN_PASSWORD_FILE") }} Admin account password file |
|
||||
{{ configsetting("INVENTREE_ADMIN_EMAIL") }} Admin account email address |
|
||||
|
||||
You can either specify the password directly using `INVENTREE_ADMIN_PASSWORD`, or you can specify a file containing the password using `INVENTREE_ADMIN_PASSWORD_FILE` (this is useful for nix users).
|
||||
|
||||
|
|
@ -238,12 +235,11 @@ A PEM-encoded file containing the oidc private key can be passed via the environ
|
|||
|
||||
If not specified via environment variables, the fallback files (automatically generated as part of InvenTree installation) will be used.
|
||||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_SECRET_KEY | secret_key | Raw secret key value | *Not specified* |
|
||||
| INVENTREE_SECRET_KEY_FILE | secret_key_file | File containing secret key value | *Not specified* |
|
||||
| INVENTREE_OIDC_PRIVATE_KEY | oidc_private_key | Raw private key value | *Not specified* |
|
||||
| INVENTREE_OIDC_PRIVATE_KEY_FILE | oidc_private_key_file | File containing private key value in PEM format | *Not specified* |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_SECRET_KEY") }} Raw secret key value |
|
||||
{{ configsetting("INVENTREE_SECRET_KEY_FILE") }} File containing secret key value |
|
||||
{{ configsetting("INVENTREE_OIDC_PRIVATE_KEY") }} Raw private key value |
|
||||
{{ configsetting("INVENTREE_OIDC_PRIVATE_KEY_FILE", default="oidc.pem") }} File containing private key value in PEM format |
|
||||
|
||||
## Database Options
|
||||
|
||||
|
|
@ -253,14 +249,14 @@ Database options are specified under the *database* heading in the configuration
|
|||
|
||||
The following database options can be configured:
|
||||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_DB_ENGINE | database.ENGINE | Database backend | *Not specified* |
|
||||
| INVENTREE_DB_NAME | database.NAME | Database name | *Not specified* |
|
||||
| INVENTREE_DB_USER | database.USER | Database username (if required) | *Not specified* |
|
||||
| INVENTREE_DB_PASSWORD | database.PASSWORD | Database password (if required) | *Not specified* |
|
||||
| INVENTREE_DB_HOST | database.HOST | Database host address (if required) | *Not specified* |
|
||||
| INVENTREE_DB_PORT | database.PORT | Database host port (if required) | *Not specified* |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_DB_ENGINE") }} Database backend |
|
||||
{{ configsetting("INVENTREE_DB_NAME") }} Database name |
|
||||
{{ configsetting("INVENTREE_DB_USER") }} Database username (if required) |
|
||||
{{ configsetting("INVENTREE_DB_PASSWORD") }} Database password (if required) |
|
||||
{{ configsetting("INVENTREE_DB_HOST") }} Database host address (if required) |
|
||||
{{ configsetting("INVENTREE_DB_PORT") }} Database host port (if required) |
|
||||
{{ configsetting("INVENTREE_DB_OPTIONS") }} Additional database options (as a JSON object) |
|
||||
|
||||
!!! 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.
|
||||
|
|
@ -269,22 +265,20 @@ The following database options can be configured:
|
|||
|
||||
If running with a PostgreSQL database backend, the following additional options are available:
|
||||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_DB_TIMEOUT | database.timeout | Database connection timeout (s) | 2 |
|
||||
| INVENTREE_DB_TCP_KEEPALIVES | database.tcp_keepalives | TCP keepalive | 1 |
|
||||
| INVENTREE_DB_TCP_KEEPALIVES_IDLE | database.tcp_keepalives_idle | Idle TCP keepalive | 1 |
|
||||
| INVENTREE_DB_TCP_KEEPALIVES_INTERVAL | database.tcp_keepalives_interval | TCP keepalive interval | 1|
|
||||
| INVENTREE_DB_TCP_KEEPALIVES_COUNT | database.tcp_keepalives_count | TCP keepalive count | 5 |
|
||||
| INVENTREE_DB_ISOLATION_SERIALIZABLE | database.serializable | Database isolation level configured to "serializable" | False |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_DB_TIMEOUT", default="2") }} Database connection timeout (s) |
|
||||
| `INVENTREE_DB_TCP_KEEPALIVES` | database.tcp_keepalives | 1 | TCP keepalive |
|
||||
| `INVENTREE_DB_TCP_KEEPALIVES_IDLE` | database.tcp_keepalives_idle | 1 | Idle TCP keepalive |
|
||||
| `INVENTREE_DB_TCP_KEEPALIVES_INTERVAL` | database.tcp_keepalives_interval | 1| TCP keepalive interval |
|
||||
| `INVENTREE_DB_TCP_KEEPALIVES_COUNT` | database.tcp_keepalives_count | 5 | TCP keepalive count |
|
||||
| `INVENTREE_DB_ISOLATION_SERIALIZABLE` | database.serializable | False | Database isolation level configured to "serializable" |
|
||||
|
||||
### MySQL Settings
|
||||
|
||||
If running with a MySQL database backend, the following additional options are available:
|
||||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_DB_ISOLATION_SERIALIZABLE | database.serializable | Database isolation level configured to "serializable" | False |
|
||||
{{ configtable() }}
|
||||
| `INVENTREE_DB_ISOLATION_SERIALIZABLE` | database.serializable | False | Database isolation level configured to "serializable" |
|
||||
|
||||
### SQLite Settings
|
||||
|
||||
|
|
@ -293,10 +287,9 @@ If running with a MySQL database backend, the following additional options are a
|
|||
|
||||
If running with a SQLite database backend, the following additional options are available:
|
||||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_DB_TIMEOUT | database.timeout | Database connection timeout (s) | 10 |
|
||||
| INVENTREE_DB_WAL_MODE | database.wal_mode | Enable Write-Ahead Logging (WAL) mode for SQLite databases | True |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_DB_TIMEOUT", default="10") }} Database connection timeout (s) |
|
||||
| `INVENTREE_DB_WAL_MODE` | database.wal_mode | True | Enable Write-Ahead Logging (WAL) mode for SQLite databases |
|
||||
|
||||
## Caching
|
||||
|
||||
|
|
@ -314,20 +307,21 @@ Enabling global caching requires connection to a redis server (which is separate
|
|||
|
||||
The following cache settings are available:
|
||||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_CACHE_ENABLED | cache.enabled | Enable redis caching | False |
|
||||
| INVENTREE_CACHE_HOST | cache.host | Cache server host | *Not specified* |
|
||||
| INVENTREE_CACHE_PORT | cache.port | Cache server port | 6379 |
|
||||
| INVENTREE_CACHE_PASSWORD | cache.password | Cache server password | none |
|
||||
| INVENTREE_CACHE_USER | cache.user | Cache server username | none |
|
||||
| INVENTREE_CACHE_CONNECT_TIMEOUT | cache.connect_timeout | Cache connection timeout (seconds) | 3 |
|
||||
| INVENTREE_CACHE_TIMEOUT | cache.timeout | Cache timeout (seconds) | 3 |
|
||||
| INVENTREE_CACHE_TCP_KEEPALIVE | cache.tcp_keepalive | Cache TCP keepalive | True |
|
||||
| INVENTREE_CACHE_KEEPALIVE_COUNT | cache.keepalive_count | Cache keepalive count | 5 |
|
||||
| INVENTREE_CACHE_KEEPALIVE_IDLE | cache.keepalive_idle | Cache keepalive idle | 1 |
|
||||
| INVENTREE_CACHE_KEEPALIVE_INTERVAL | cache.keepalive_interval | Cache keepalive interval | 1 |
|
||||
| INVENTREE_CACHE_USER_TIMEOUT | cache.user_timeout | Cache user timeout | 1000 |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_CACHE_ENABLED") }} Enable redis caching |
|
||||
{{ configsetting("INVENTREE_CACHE_HOST") }} Cache server host |
|
||||
{{ configsetting("INVENTREE_CACHE_PORT") }} Cache server port |
|
||||
{{ configsetting("INVENTREE_CACHE_PASSWORD") }} Cache server password |
|
||||
{{ configsetting("INVENTREE_CACHE_USER") }} Cache server username |
|
||||
{{ configsetting("INVENTREE_CACHE_DB") }} Cache server database index |
|
||||
{{ configsetting("INVENTREE_CACHE_CONNECT_TIMEOUT") }} Cache connection timeout (seconds) |
|
||||
{{ configsetting("INVENTREE_CACHE_TIMEOUT") }} Cache timeout (seconds) |
|
||||
{{ configsetting("INVENTREE_CACHE_TCP_KEEPALIVE") }} Cache TCP keepalive |
|
||||
{{ configsetting("INVENTREE_CACHE_KEEPALIVE_COUNT") }} Cache keepalive count |
|
||||
{{ configsetting("INVENTREE_CACHE_KEEPALIVE_IDLE") }} Cache keepalive idle |
|
||||
{{ configsetting("INVENTREE_CACHE_KEEPALIVE_INTERVAL") }} Cache keepalive interval |
|
||||
{{ configsetting("INVENTREE_CACHE_USER_TIMEOUT") }} Cache user timeout |
|
||||
|
||||
|
||||
!!! 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.
|
||||
|
|
@ -338,17 +332,16 @@ To enable [email functionality](../settings/email.md), email settings must be co
|
|||
|
||||
The following email settings are available:
|
||||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_EMAIL_BACKEND | email.backend | Email backend module | django.core.mail.backends.smtp.EmailBackend |
|
||||
| INVENTREE_EMAIL_HOST | email.host | Email server host | *Not specified* |
|
||||
| INVENTREE_EMAIL_PORT | email.port | Email server port | 25 |
|
||||
| INVENTREE_EMAIL_USERNAME | email.username | Email account username | *Not specified* |
|
||||
| INVENTREE_EMAIL_PASSWORD | email.password | Email account password | *Not specified* |
|
||||
| INVENTREE_EMAIL_TLS | email.tls | Enable STARTTLS support (commonly port 567) | False |
|
||||
| INVENTREE_EMAIL_SSL | email.ssl | Enable legacy SSL/TLS support (commonly port 465) | False |
|
||||
| INVENTREE_EMAIL_SENDER | email.sender | Sending email address | *Not specified* |
|
||||
| INVENTREE_EMAIL_PREFIX | email.prefix | Prefix for subject text | [InvenTree] |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_EMAIL_BACKEND") }} Email backend module |
|
||||
{{ configsetting("INVENTREE_EMAIL_HOST") }} Email server host |
|
||||
{{ configsetting("INVENTREE_EMAIL_PORT") }} Email server port |
|
||||
{{ configsetting("INVENTREE_EMAIL_USERNAME") }} Email account username |
|
||||
{{ configsetting("INVENTREE_EMAIL_PASSWORD") }} Email account password |
|
||||
{{ configsetting("INVENTREE_EMAIL_TLS") }} Enable STARTTLS support (commonly port 567) |
|
||||
{{ configsetting("INVENTREE_EMAIL_SSL") }} Enable legacy SSL/TLS support (commonly port 465) |
|
||||
{{ configsetting("INVENTREE_EMAIL_SENDER") }} Sending email address |
|
||||
{{ configsetting("INVENTREE_EMAIL_PREFIX") }} Prefix for subject text |
|
||||
|
||||
### Email Backend
|
||||
|
||||
|
|
@ -365,11 +358,10 @@ The "sender" email address is the address from which InvenTree emails are sent (
|
|||
|
||||
InvenTree requires some external directories for storing files:
|
||||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_STATIC_ROOT | static_root | [Static files](./processes.md#static-files) directory | *Not specified* |
|
||||
| INVENTREE_MEDIA_ROOT | media_root | [Media files](./processes.md#media-files) directory | *Not specified* |
|
||||
| INVENTREE_BACKUP_DIR | backup_dir | Backup files directory | *Not specified* |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_STATIC_ROOT") }} [Static files](./processes.md#static-files) directory |
|
||||
{{ configsetting("INVENTREE_MEDIA_ROOT") }} [Media files](./processes.md#media-files) directory |
|
||||
{{ configsetting("INVENTREE_BACKUP_DIR") }} Directory for backup files |
|
||||
|
||||
!!! tip "Serving Files"
|
||||
Read the [proxy server documentation](./processes.md#proxy-server) for more information on hosting *static* and *media* files
|
||||
|
|
@ -403,43 +395,41 @@ Alternatively this location can be specified with the `INVENTREE_BACKUP_DIR` env
|
|||
|
||||
It is also possible to use alternative storage backends for static and media files, at the moment there is direct provide direct support bundled for S3 and SFTP. Google cloud storage and Azure blob storage would also be supported by the [used library](https://django-storages.readthedocs.io), but require additional packages to be installed.
|
||||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_STORAGE_TARGET | storage.target | Storage target to use for static and media files, valid options: local, s3, sftp | local |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_STORAGE_TARGET") }} Storage target to use for static and media files, valid options: local, s3, sftp |
|
||||
|
||||
|
||||
#### S3
|
||||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_S3_ACCESS_KEY | storage.s3.access_key | Access key | *Not specified* |
|
||||
| INVENTREE_S3_SECRET_KEY | storage.s3.secret_key | Secret key | *Not specified* |
|
||||
| INVENTREE_S3_BUCKET_NAME | storage.s3.bucket_name | Bucket name, required by most providers | *Not specified* |
|
||||
| INVENTREE_S3_REGION_NAME | storage.s3.region_name | S3 region name | *Not specified* |
|
||||
| INVENTREE_S3_ENDPOINT_URL | storage.s3.endpoint_url | Custom S3 endpoint URL, defaults to AWS endpoints if not set | *Not specified* |
|
||||
| INVENTREE_S3_LOCATION | storage.s3.location | Sub-Location that should be used | inventree-server |
|
||||
| INVENTREE_S3_DEFAULT_ACL | storage.s3.default_acl | Default ACL for uploaded files, defaults to provider default if not set | *Not specified* |
|
||||
| INVENTREE_S3_VERIFY_SSL | storage.s3.verify_ssl | Verify SSL certificate for S3 endpoint | True |
|
||||
| INVENTREE_S3_OVERWRITE | storage.s3.overwrite | Overwrite existing files in S3 bucket | False |
|
||||
| INVENTREE_S3_VIRTUAL | storage.s3.virtual | Use virtual addressing style - by default False -> `path` style, `virtual` style if True | False |
|
||||
{{ configtable() }}
|
||||
| `INVENTREE_S3_ACCESS_KEY` | storage.s3.access_key | *Not specified* | Access key |
|
||||
| `INVENTREE_S3_SECRET_KEY` | storage.s3.secret_key | *Not specified* | Secret key |
|
||||
| `INVENTREE_S3_BUCKET_NAME` | storage.s3.bucket_name | *Not specified* | Bucket name, required by most providers |
|
||||
| `INVENTREE_S3_REGION_NAME` | storage.s3.region_name | *Not specified* | S3 region name |
|
||||
| `INVENTREE_S3_ENDPOINT_URL` | storage.s3.endpoint_url | *Not specified* | Custom S3 endpoint URL, defaults to AWS endpoints if not set |
|
||||
| `INVENTREE_S3_LOCATION` | storage.s3.location | inventree-server | Sub-Location that should be used |
|
||||
| `INVENTREE_S3_DEFAULT_ACL` | storage.s3.default_acl | *Not specified* | Default ACL for uploaded files, defaults to provider default if not set |
|
||||
| `INVENTREE_S3_VERIFY_SSL` | storage.s3.verify_ssl | True | Verify SSL certificate for S3 endpoint |
|
||||
| `INVENTREE_S3_OVERWRITE` | storage.s3.overwrite | False | Overwrite existing files in S3 bucket |
|
||||
| `INVENTREE_S3_VIRTUAL` | storage.s3.virtual | False | Use virtual addressing style - by default False -> `path` style, `virtual` style if True |
|
||||
|
||||
#### SFTP
|
||||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_SFTP_HOST | storage.sftp.host | SFTP host | *Not specified* |
|
||||
| INVENTREE_SFTP_PARAMS | storage.sftp.params | SFTP connection parameters, see https://docs.paramiko.org/en/latest/api/client.html#paramiko.client.SSHClient.connect; e.g. `{'port': 22, 'user': 'usr', 'password': 'pwd'}` | *Not specified* |
|
||||
| INVENTREE_SFTP_UID | storage.sftp.uid | SFTP user ID - not required | *Not specified* |
|
||||
| INVENTREE_SFTP_GID | storage.sftp.gid | SFTP group ID - not required | *Not specified* |
|
||||
| INVENTREE_SFTP_LOCATION | storage.sftp.location | Sub-Location that should be used | inventree-server |
|
||||
{{ configtable() }}
|
||||
| `INVENTREE_SFTP_HOST` | storage.sftp.host | *Not specified* | SFTP host |
|
||||
| `INVENTREE_SFTP_PARAMS` | storage.sftp.params | *Not specified* | SFTP connection parameters, see https://docs.paramiko.org/en/latest/api/client.html#paramiko.client.SSHClient.connect; e.g. `{'port': 22, 'user': 'usr', 'password': 'pwd'}` |
|
||||
| `INVENTREE_SFTP_UID` | storage.sftp.uid | *Not specified* | SFTP user ID - not required |
|
||||
| `INVENTREE_SFTP_GID` | storage.sftp.gid | *Not specified* | SFTP group ID - not required |
|
||||
| `INVENTREE_SFTP_LOCATION` | storage.sftp.location | inventree-server | Sub-Location that should be used |
|
||||
|
||||
## Authentication
|
||||
|
||||
InvenTree provides allowance for additional sign-in options. The following options are not enabled by default, and care must be taken by the system administrator when configuring these settings.
|
||||
|
||||
| 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 |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_MFA_ENABLED") }} Enable multi-factor authentication support for the InvenTree server |
|
||||
{{ configsetting("INVENTREE_MFA_SUPPORTED_TYPES") }} List of supported multi-factor authentication types |
|
||||
{{ configsetting("INVENTREE_USE_JWT") }} Enable support for JSON Web Tokens (JWT) for authentication |
|
||||
|
||||
### Single Sign On
|
||||
|
||||
|
|
@ -454,11 +444,10 @@ There are multiple configuration parameters which must be specified (either in y
|
|||
|
||||
The login-experience can be altered with the following settings:
|
||||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_LOGIN_CONFIRM_DAYS | login_confirm_days | Duration for which confirmation links are valid | 3 |
|
||||
| INVENTREE_LOGIN_ATTEMPTS | login_attempts | Count of allowed login attempts before blocking user | 5 |
|
||||
| INVENTREE_LOGIN_DEFAULT_HTTP_PROTOCOL | login_default_protocol | Default protocol to use for login callbacks (e.g. using [SSO](#single-sign-on)) | Uses the protocol specified in `INVENTREE_SITE_URL`, or defaults to *http* |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_LOGIN_CONFIRM_DAYS") }} Duration for which confirmation links are valid |
|
||||
{{ configsetting("INVENTREE_LOGIN_ATTEMPTS") }} Count of allowed login attempts before blocking user |
|
||||
{{ configsetting("INVENTREE_LOGIN_DEFAULT_HTTP_PROTOCOL", default="Uses the protocol specified in `INVENTREE_SITE_URL`, or defaults to *http*") }} Default protocol to use for login callbacks (e.g. using [SSO](#single-sign-on)) |
|
||||
|
||||
!!! tip "Default Protocol"
|
||||
If you have specified `INVENTREE_SITE_URL`, the default protocol will be used from that setting. Otherwise, the default protocol will be *http*.
|
||||
|
|
@ -471,22 +460,20 @@ Custom authentication backends can be used by specifying them here. These can fo
|
|||
|
||||
The following options are available for configuring the InvenTree [background worker process](./processes.md#background-worker):
|
||||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_BACKGROUND_WORKERS | background.workers | Number of background worker processes | 1 |
|
||||
| INVENTREE_BACKGROUND_TIMEOUT | background.timeout | Timeout for background worker tasks (seconds) | 90 |
|
||||
| INVENTREE_BACKGROUND_RETRY | background.retry | Time to wait before retrying a background task (seconds) | 300 |
|
||||
| INVENTREE_BACKGROUND_MAX_ATTEMPTS | background.max_attempts | Maximum number of attempts for a background task | 5 |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_BACKGROUND_WORKERS") }} Number of background worker processes |
|
||||
{{ configsetting("INVENTREE_BACKGROUND_TIMEOUT") }} Timeout for background worker tasks (seconds) |
|
||||
{{ configsetting("INVENTREE_BACKGROUND_RETRY") }} Time to wait before retrying a background task (seconds) |
|
||||
{{ configsetting("INVENTREE_BACKGROUND_MAX_ATTEMPTS") }} Maximum number of attempts for a background task |
|
||||
|
||||
## Sentry Integration
|
||||
|
||||
The InvenTree server can be integrated with the [sentry.io](https://sentry.io) monitoring service, for error logging and performance tracking.
|
||||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_SENTRY_ENABLED | sentry_enabled | Enable sentry.io integration | False |
|
||||
| INVENTREE_SENTRY_DSN | sentry_dsn | Sentry DSN (data source name) key | *Defaults to InvenTree developer key* |
|
||||
| INVENTREE_SENTRY_SAMPLE_RATE | sentry_sample_rate | How often to send data samples | 0.1 |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_SENTRY_ENABLED") }} Enable sentry.io integration |
|
||||
{{ configsetting("INVENTREE_SENTRY_DSN", default="Defaults to InvenTree developer key") }} Sentry DSN (data source name) key |
|
||||
{{ configsetting("INVENTREE_SENTRY_SAMPLE_RATE") }} How often to send data samples (seconds) |
|
||||
|
||||
!!! info "Default DSN"
|
||||
If enabled with the default DSN, server errors will be logged to a sentry.io account monitored by the InvenTree developers.
|
||||
|
|
@ -495,13 +482,11 @@ The InvenTree server can be integrated with the [sentry.io](https://sentry.io) m
|
|||
|
||||
The logo and custom messages can be changed/set:
|
||||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_CUSTOM_LOGO | customize.logo | Path to custom logo in the static files directory | *Not specified* |
|
||||
| INVENTREE_CUSTOM_SPLASH | customize.splash | Path to custom splash screen in the static files directory | *Not specified* |
|
||||
| INVENTREE_CUSTOMIZE | customize.site_header | Custom site header in the Django admin | InvenTree Admin |
|
||||
| INVENTREE_CUSTOMIZE | customize.login_message | Custom message for login page | *Not specified* |
|
||||
| INVENTREE_CUSTOMIZE | customize.navbar_message | Custom message for navbar | *Not specified* |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_CUSTOM_LOGO") }} Path to custom logo in the static files directory |
|
||||
{{ configsetting("INVENTREE_CUSTOM_SPLASH") }} Path to custom splash screen in the static files directory |
|
||||
{{ configsetting("INVENTREE_SITE_HEADER") }} Custom header text for the django admin page |
|
||||
{{ configsetting("INVENTREE_CUSTOMIZE") }} JSON object containing custom messages for the login page, navbar, and Django admin site |
|
||||
|
||||
The INVENTREE_CUSTOMIZE environment variable must contain a json object with the keys from the table above and
|
||||
the wanted values. Example:
|
||||
|
|
@ -546,15 +531,15 @@ INVENTREE_FRONTEND_SETTINGS='{"mobile_mode": "allow-ignore"}'
|
|||
|
||||
The following [plugin](../plugins/index.md) configuration options are available:
|
||||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_PLUGINS_ENABLED | plugins_enabled | Enable plugin support | False |
|
||||
| INVENTREE_PLUGIN_NOINSTALL | plugin_noinstall | Disable Plugin installation via API - only use plugins.txt file | False |
|
||||
| INVENTREE_PLUGIN_FILE | plugins_plugin_file | Location of plugin installation file | *Not specified* |
|
||||
| INVENTREE_PLUGIN_DIR | plugins_plugin_dir | Location of external plugin directory | *Not specified* |
|
||||
| INVENTREE_PLUGINS_MANDATORY | plugins_mandatory | List of [plugins which are considered mandatory](../plugins/index.md#mandatory-third-party-plugins) | *Not specified* |
|
||||
| INVENTREE_PLUGIN_DEV_SLUG | plugin_dev.slug | Specify plugin to run in [development mode](../plugins/creator.md#backend-configuration) | *Not specified* |
|
||||
| INVENTREE_PLUGIN_DEV_HOST | plugin_dev.host | Specify host for development mode plugin | http://localhost:5174 |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_PLUGINS_ENABLED") }} Enable plugin support |
|
||||
{{ configsetting("INVENTREE_PLUGIN_NOINSTALL") }} Disable Plugin installation via API |
|
||||
{{ configsetting("INVENTREE_PLUGIN_FILE") }} Location of plugin installation file |
|
||||
| `INVENTREE_PLUGIN_DIR` | `plugin_dir` | *Not specified* | Location of external plugin directory |
|
||||
{{ configsetting("INVENTREE_PLUGIN_RETRY") }} Number of tries to attempt loading a plugin before giving up |
|
||||
{{ configsetting("INVENTREE_PLUGINS_MANDATORY") }} List of [plugins which are considered mandatory](../plugins/index.md#mandatory-third-party-plugins) |
|
||||
{{ configsetting("INVENTREE_PLUGIN_DEV_SLUG") }} Specify plugin to run in [development mode](../plugins/creator.md#backend-configuration) |
|
||||
{{ configsetting("INVENTREE_PLUGIN_DEV_HOST") }} Specify host for development mode plugin |
|
||||
|
||||
## Override Global Settings
|
||||
|
||||
|
|
@ -562,6 +547,12 @@ If required, [global settings values](../settings/global.md#override-global-sett
|
|||
|
||||
To override global settings, provide a "dictionary" of settings overrides in the configuration file, or via an environment variable.
|
||||
|
||||
| Environment Variable | Configuration File | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_GLOBAL_SETTINGS | global_settings | JSON object containing global settings overrides | *Not specified* |
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_GLOBAL_SETTINGS") }} JSON object containing global settings overrides |
|
||||
|
||||
## Other Settings
|
||||
|
||||
Other available settings, not categorized above, are detailed in the table below:
|
||||
|
||||
{{ configtable() }}
|
||||
{{ configsetting("INVENTREE_EXTRA_URL_SCHEMES") }} Allow additional URL schemes for URL validation |
|
||||
|
|
|
|||
|
|
@ -87,13 +87,7 @@ If you are creating the initial database, you need to create an admin (superuser
|
|||
docker compose run inventree-server invoke superuser
|
||||
```
|
||||
|
||||
Alternatively, admin account details can be specified in the `.env` file, removing the need for this manual step:
|
||||
|
||||
| Variable | Description |
|
||||
| --- | --- |
|
||||
| INVENTREE_ADMIN_USER | Admin account username |
|
||||
| INVENTREE_ADMIN_PASSWORD | Admin account password |
|
||||
| INVENTREE_ADMIN_EMAIL | Admin account email address |
|
||||
Alternatively, admin account details can be specified using environment variables, or in the `.env` file, removing the need for this manual step. Refer to the [configuration documentation](./config.md#administrator-account) for more information.
|
||||
|
||||
!!! warning "Scrub Account Data"
|
||||
Ensure that the admin account credentials are removed from the `.env` file after the first run, for security.
|
||||
|
|
@ -241,9 +235,9 @@ By default, the Dockerized InvenTree web server binds to all available network i
|
|||
This can be adjusted using the following environment variables:
|
||||
|
||||
| Environment Variable | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| INVENTREE_WEB_ADDR | 0.0.0.0 |
|
||||
| INVENTREE_WEB_PORT | 8000 |
|
||||
| --- | --- |
|
||||
| `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.
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,15 @@ title: InvenTree Installer
|
|||
---
|
||||
|
||||
## Installer
|
||||
|
||||
The InvenTree installer automates the installation procedure for a production InvenTree server.
|
||||
|
||||
Supported OSs are Debian 10, 11, 12 and Ubuntu 20.04 LTS, 22.04 LTS, 24.04 LTS.
|
||||
The installer supports the following Linux distributions:
|
||||
|
||||
- Debian 11, 12
|
||||
- Ubuntu 22.04 LTS, 24.04 LTS
|
||||
|
||||
Support for other OS versions is not currently planned. If you are using a different distribution, you can still follow the [docker](./docker.md) or [bare metal](./install.md) installation instructions.
|
||||
|
||||
### Quick Script
|
||||
|
||||
|
|
|
|||
30
docs/main.py
|
|
@ -34,6 +34,7 @@ for key in [
|
|||
print(f' - {key}: {val}')
|
||||
|
||||
# Cached settings dict values
|
||||
global CONFIG_SETTINGS
|
||||
global GLOBAL_SETTINGS
|
||||
global USER_SETTINGS
|
||||
global TAGS
|
||||
|
|
@ -64,6 +65,7 @@ with open(settings_file, encoding='utf-8') as sf:
|
|||
|
||||
GLOBAL_SETTINGS = settings['global']
|
||||
USER_SETTINGS = settings['user']
|
||||
CONFIG_SETTINGS = settings['config']
|
||||
|
||||
# Tags
|
||||
with open(gen_base.joinpath('inventree_tags.yml'), encoding='utf-8') as f:
|
||||
|
|
@ -377,6 +379,34 @@ def define_env(env):
|
|||
|
||||
return rendersetting(key, setting, short=short)
|
||||
|
||||
@env.macro
|
||||
def configtable():
|
||||
"""Generate a header for the configuration settings table."""
|
||||
return '| Environment Variable | Configuration File | Default | Description |\n| --- | --- | --- | --- |'
|
||||
|
||||
@env.macro
|
||||
def configsetting(key: str, default: Optional[str] = None):
|
||||
"""Extract information on a particular configuration setting.
|
||||
|
||||
Arguments:
|
||||
key: The name of the configuration setting to extract information for.
|
||||
default: An optional default value to override the setting's default display value.
|
||||
"""
|
||||
global CONFIG_SETTINGS
|
||||
setting = CONFIG_SETTINGS[key]
|
||||
|
||||
observe_setting(key, 'config')
|
||||
|
||||
cfg_key = setting.get('config_key', None)
|
||||
cfg_key = f'`{cfg_key}`' if cfg_key else '-'
|
||||
|
||||
default = default or setting.get('default_value', None)
|
||||
|
||||
if default is None:
|
||||
default = '*Not Specified*'
|
||||
|
||||
return f'| <span title="{key}" style="white-space: nowrap;"><code>{key}</code></span> | {cfg_key} | {default} |'
|
||||
|
||||
@env.macro
|
||||
def tags_and_filters():
|
||||
"""Return a list of all tags and filters."""
|
||||
|
|
|
|||
|
|
@ -150,6 +150,7 @@ nav:
|
|||
- Bill of Materials: manufacturing/bom.md
|
||||
- Build Orders: manufacturing/build.md
|
||||
- Build Outputs: manufacturing/output.md
|
||||
- Required Quantity: manufacturing/required.md
|
||||
- Allocating Stock: manufacturing/allocate.md
|
||||
- External Manufacturing: manufacturing/external.md
|
||||
- Example Build Order: manufacturing/example.md
|
||||
|
|
@ -263,6 +264,7 @@ nav:
|
|||
- Stock: app/stock.md
|
||||
- Purchase Orders: app/po.md
|
||||
- Sales Orders: app/so.md
|
||||
- Build Orders: app/build.md
|
||||
- Settings: app/settings.md
|
||||
- Privacy: app/privacy.md
|
||||
- Translation: app/translation.md
|
||||
|
|
|
|||
|
|
@ -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.3
|
||||
mkdocstrings[python]<=1.0.4,>=1.0.4
|
||||
mkdocs-mermaid2-plugin
|
||||
|
|
|
|||
|
|
@ -169,9 +169,9 @@ charset-normalizer==3.4.7 \
|
|||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# requests
|
||||
click==8.3.1 \
|
||||
--hash=sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a \
|
||||
--hash=sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6
|
||||
click==8.3.2 \
|
||||
--hash=sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5 \
|
||||
--hash=sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d
|
||||
# via
|
||||
# mkdocs
|
||||
# neoteroi-mkdocs
|
||||
|
|
@ -202,9 +202,9 @@ gitdb==4.0.12 \
|
|||
--hash=sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571 \
|
||||
--hash=sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf
|
||||
# via gitpython
|
||||
gitpython==3.1.46 \
|
||||
--hash=sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f \
|
||||
--hash=sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058
|
||||
gitpython==3.1.47 \
|
||||
--hash=sha256:489f590edfd6d20571b2c0e72c6a6ac6915ee8b8cd04572330e3842207a78905 \
|
||||
--hash=sha256:dba27f922bd2b42cb54c87a8ab3cb6beb6bf07f3d564e21ac848913a05a8a3cd
|
||||
# via mkdocs-git-revision-date-localized-plugin
|
||||
griffelib==2.0.0 \
|
||||
--hash=sha256:01284878c966508b6d6f1dbff9b6fa607bc062d8261c5c7253cb285b06422a7f
|
||||
|
|
@ -430,9 +430,9 @@ mkdocs-simple-hooks==0.1.5 \
|
|||
--hash=sha256:dddbdf151a18723c9302a133e5cf79538be8eb9d274e8e07d2ac3ac34890837c \
|
||||
--hash=sha256:efeabdbb98b0850a909adee285f3404535117159d5cb3a34f541d6eaa644d50a
|
||||
# via -r docs/requirements.in
|
||||
mkdocstrings[python]==1.0.3 \
|
||||
--hash=sha256:0d66d18430c2201dc7fe85134277382baaa15e6b30979f3f3bdbabd6dbdb6046 \
|
||||
--hash=sha256:ab670f55040722b49bb45865b2e93b824450fb4aef638b00d7acb493a9020434
|
||||
mkdocstrings[python]==1.0.4 \
|
||||
--hash=sha256:3969a6515b77db65fd097b53c1b7aa4ae840bd71a2ee62a6a3e89503446d7172 \
|
||||
--hash=sha256:63464b4b29053514f32a1dbbf604e52876d5e638111b0c295ab7ed3cac73ca9b
|
||||
# via
|
||||
# -r docs/requirements.in
|
||||
# mkdocstrings-python
|
||||
|
|
@ -444,9 +444,9 @@ neoteroi-mkdocs==1.2.0 \
|
|||
--hash=sha256:58e25cb1b9db093ffa8d12bdb33264bf567cac30fb964b56e0a493efa749ad6e \
|
||||
--hash=sha256:91b6aa95a4e46c9bb93e00e021d2044cb0c7d80c0b4600434ff8f440d613a0a8
|
||||
# via -r docs/requirements.in
|
||||
packaging==26.0 \
|
||||
--hash=sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4 \
|
||||
--hash=sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529
|
||||
packaging==26.1 \
|
||||
--hash=sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f \
|
||||
--hash=sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# mkdocs
|
||||
|
|
@ -463,9 +463,9 @@ pathspec==1.0.4 \
|
|||
# mkdocs
|
||||
# mkdocs-macros-plugin
|
||||
# properdocs
|
||||
platformdirs==4.9.4 \
|
||||
--hash=sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934 \
|
||||
--hash=sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868
|
||||
platformdirs==4.9.6 \
|
||||
--hash=sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a \
|
||||
--hash=sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# mkdocs-get-deps
|
||||
|
|
@ -591,9 +591,9 @@ requests==2.33.1 \
|
|||
# mkdocs-macros-plugin
|
||||
# mkdocs-material
|
||||
# mkdocs-mermaid2-plugin
|
||||
rich==14.3.3 \
|
||||
--hash=sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d \
|
||||
--hash=sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b
|
||||
rich==15.0.0 \
|
||||
--hash=sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb \
|
||||
--hash=sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36
|
||||
# via neoteroi-mkdocs
|
||||
setuptools==82.0.1 \
|
||||
--hash=sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9 \
|
||||
|
|
|
|||
|
|
@ -109,12 +109,13 @@ unresolved-reference="ignore" # 21 # see https://github.com/astral-sh/ty/issues
|
|||
unresolved-attribute="ignore" # 505 # need Plugin Mixin typing
|
||||
call-non-callable="ignore" # 8 ##
|
||||
invalid-assignment="ignore" # 17 # need to wait for better django field stubs
|
||||
invalid-method-override="ignore" # 99
|
||||
# invalid-method-override="ignore" # 99
|
||||
invalid-return-type="ignore" # 22 ##
|
||||
possibly-missing-attribute="ignore" # 25 # https://github.com/astral-sh/ty/issues/164
|
||||
# possibly-missing-attribute="ignore" # 25 # https://github.com/astral-sh/ty/issues/164
|
||||
unknown-argument="ignore" # 3 # need to wait for better django field stubs
|
||||
invalid-argument-type="ignore" # 49
|
||||
no-matching-overload="ignore" # 3 # need to wait for betterdjango field stubs
|
||||
possibly-unbound-attribute="ignore" # 21
|
||||
|
||||
[tool.coverage.run]
|
||||
source = ["src/backend/InvenTree", "InvenTree"]
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ class InfoView(APIView):
|
|||
'instance': InvenTree.version.inventreeInstanceName(),
|
||||
'apiVersion': InvenTree.version.inventreeApiVersion(),
|
||||
'worker_running': is_worker_running(),
|
||||
'worker_count': settings.BACKGROUND_WORKER_COUNT,
|
||||
'worker_count': settings.Q_CLUSTER['workers'],
|
||||
'worker_pending_tasks': self.worker_pending_tasks(),
|
||||
'plugins_enabled': settings.PLUGINS_ENABLED,
|
||||
'plugins_install_disabled': settings.PLUGINS_INSTALL_DISABLED,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,25 @@
|
|||
"""InvenTree API version information."""
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 478
|
||||
INVENTREE_API_VERSION = 482
|
||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||
|
||||
INVENTREE_API_TEXT = """
|
||||
|
||||
v482 -> 2026-03-15 : https://github.com/inventree/InvenTree/pull/11540
|
||||
- Add id to the ordering fields of the Parts model
|
||||
|
||||
v481 -> 2026-04-28 : https://github.com/inventree/InvenTree/pull/11825
|
||||
- Adds new "bom" ruleset and associated permissions for BOM management, separate from the "part" ruleset which remains focused on part management
|
||||
|
||||
v480 -> 2026-04-27 : https://github.com/inventree/InvenTree/pull/11816
|
||||
- The "issued_by" field on the Build API endpoint is now read-only, and is automatically set to the current user when a build is created
|
||||
|
||||
v479 -> 2026-04-11 : https://github.com/inventree/InvenTree/pull/11723
|
||||
- POST /api/notifications/readall/ now requires a POST action
|
||||
- POST /api/admin/email/test/ - now returns a 200 on. a successful test
|
||||
- GET /api/notifications/ - now uses user-centric permissions, not a general read
|
||||
|
||||
v478 -> 2026-04-11 : https://github.com/inventree/InvenTree/pull/11073
|
||||
- Add OptionalField class for cleaner handling of optional fields in serializers
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,11 @@ def cache_user():
|
|||
return cache_setting('user', None)
|
||||
|
||||
|
||||
def cache_db():
|
||||
"""Return the cache database index."""
|
||||
return cache_setting('db', 0, typecast=int)
|
||||
|
||||
|
||||
def is_global_cache_enabled() -> bool:
|
||||
"""Check if the global cache is enabled.
|
||||
|
||||
|
|
@ -89,49 +94,51 @@ def get_cache_config(global_cache: bool) -> dict:
|
|||
Returns:
|
||||
A dictionary containing the cache configuration options.
|
||||
"""
|
||||
if global_cache:
|
||||
# Build Redis URL with optional password
|
||||
password = cache_password()
|
||||
user = cache_user() or ''
|
||||
# Build Redis URL with optional password
|
||||
password = cache_password()
|
||||
user = cache_user() or ''
|
||||
host = cache_host()
|
||||
port = cache_port()
|
||||
db = cache_db()
|
||||
|
||||
if password:
|
||||
redis_url = f'redis://{user}:{password}@{cache_host()}:{cache_port()}/0'
|
||||
else:
|
||||
redis_url = f'redis://{cache_host()}:{cache_port()}/0'
|
||||
if password:
|
||||
redis_url = f'redis://{user}:{password}@{host}:{port}/{db}'
|
||||
else:
|
||||
redis_url = f'redis://{host}:{port}/{db}'
|
||||
|
||||
keepalive_options = {
|
||||
'TCP_KEEPCNT': cache_setting('keepalive_count', 5, typecast=int),
|
||||
'TCP_KEEPIDLE': cache_setting('keepalive_idle', 1, typecast=int),
|
||||
'TCP_KEEPINTVL': cache_setting('keepalive_interval', 1, typecast=int),
|
||||
'TCP_USER_TIMEOUT': cache_setting('user_timeout', 1000, typecast=int),
|
||||
}
|
||||
keepalive_options = {
|
||||
'TCP_KEEPCNT': cache_setting('keepalive_count', 5, typecast=int),
|
||||
'TCP_KEEPIDLE': cache_setting('keepalive_idle', 1, typecast=int),
|
||||
'TCP_KEEPINTVL': cache_setting('keepalive_interval', 1, typecast=int),
|
||||
'TCP_USER_TIMEOUT': cache_setting('user_timeout', 1000, typecast=int),
|
||||
}
|
||||
|
||||
return {
|
||||
'BACKEND': 'django_redis.cache.RedisCache',
|
||||
'LOCATION': redis_url,
|
||||
'OPTIONS': {
|
||||
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
|
||||
'SOCKET_CONNECT_TIMEOUT': cache_setting(
|
||||
'connect_timeout', 5, typecast=int
|
||||
),
|
||||
'SOCKET_TIMEOUT': cache_setting('timeout', 3, typecast=int),
|
||||
'CONNECTION_POOL_KWARGS': {
|
||||
'socket_keepalive': cache_setting(
|
||||
'tcp_keepalive', True, typecast=bool
|
||||
),
|
||||
'socket_keepalive_options': {
|
||||
# Only include options which are available on this platform
|
||||
# e.g. MacOS does not have TCP_KEEPIDLE and TCP_USER_TIMEOUT
|
||||
getattr(socket, key): value
|
||||
for key, value in keepalive_options.items()
|
||||
if hasattr(socket, key)
|
||||
},
|
||||
global_cache_config = {
|
||||
'BACKEND': 'django_redis.cache.RedisCache',
|
||||
'LOCATION': redis_url,
|
||||
'OPTIONS': {
|
||||
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
|
||||
'SOCKET_CONNECT_TIMEOUT': cache_setting('connect_timeout', 5, typecast=int),
|
||||
'SOCKET_TIMEOUT': cache_setting('timeout', 3, typecast=int),
|
||||
'CONNECTION_POOL_KWARGS': {
|
||||
'socket_keepalive': cache_setting('tcp_keepalive', True, typecast=bool),
|
||||
'socket_keepalive_options': {
|
||||
# Only include options which are available on this platform
|
||||
# e.g. MacOS does not have TCP_KEEPIDLE and TCP_USER_TIMEOUT
|
||||
getattr(socket, key): value
|
||||
for key, value in keepalive_options.items()
|
||||
if hasattr(socket, key)
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
# Default: Use django local memory cache
|
||||
return {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'}
|
||||
if global_cache:
|
||||
# Only return the global cache configuration if the global cache is enabled
|
||||
return global_cache_config
|
||||
else:
|
||||
# Default: Use django local memory cache
|
||||
return {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'}
|
||||
|
||||
|
||||
def create_session_cache(request) -> None:
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import shutil
|
|||
import string
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
logger = logging.getLogger('inventree')
|
||||
CONFIG_DATA = None
|
||||
|
|
@ -254,6 +254,24 @@ def do_typecast(value, type, var_name=None):
|
|||
return value
|
||||
|
||||
|
||||
def get_config_value(config_key: str) -> Optional[Any]:
|
||||
"""Helper function to retrieve a configuration value from the config file."""
|
||||
cfg_data = load_config_data()
|
||||
|
||||
result = None
|
||||
|
||||
# Hack to allow 'path traversal' in configuration file
|
||||
for key in config_key.strip().split('.'):
|
||||
if type(cfg_data) is not dict or key not in cfg_data:
|
||||
result = None
|
||||
break
|
||||
|
||||
result = cfg_data[key]
|
||||
cfg_data = cfg_data[key]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_setting(env_var=None, config_key=None, default_value=None, typecast=None):
|
||||
"""Helper function for retrieving a configuration setting value.
|
||||
|
||||
|
|
@ -270,10 +288,13 @@ def get_setting(env_var=None, config_key=None, default_value=None, typecast=None
|
|||
|
||||
def set_metadata(source: str):
|
||||
"""Set lookup metadata for the setting."""
|
||||
global CONFIG_LOOKUPS
|
||||
|
||||
key = env_var or config_key
|
||||
CONFIG_LOOKUPS[key] = {
|
||||
'env_var': env_var,
|
||||
'config_key': config_key,
|
||||
'default_value': default_value,
|
||||
'source': source,
|
||||
'accessed': datetime.datetime.now(),
|
||||
}
|
||||
|
|
@ -288,18 +309,7 @@ def get_setting(env_var=None, config_key=None, default_value=None, typecast=None
|
|||
|
||||
# Next, try to load from configuration file
|
||||
if config_key is not None:
|
||||
cfg_data = load_config_data()
|
||||
|
||||
result = None
|
||||
|
||||
# Hack to allow 'path traversal' in configuration file
|
||||
for key in config_key.strip().split('.'):
|
||||
if type(cfg_data) is not dict or key not in cfg_data:
|
||||
result = None
|
||||
break
|
||||
|
||||
result = cfg_data[key]
|
||||
cfg_data = cfg_data[key]
|
||||
result = get_config_value(config_key)
|
||||
|
||||
if result is not None:
|
||||
set_metadata('yaml')
|
||||
|
|
@ -544,13 +554,14 @@ def get_frontend_settings(debug=True):
|
|||
'INVENTREE_FRONTEND_SETTINGS', 'frontend_settings', {}, typecast=dict
|
||||
)
|
||||
|
||||
base_url = get_setting(
|
||||
'INVENTREE_FRONTEND_URL_BASE', 'frontend_url_base', 'web', typecast=str
|
||||
)
|
||||
|
||||
# Set the base URL for the user interface
|
||||
# This is the UI path e.g. '/web/'
|
||||
if 'base_url' not in frontend_settings:
|
||||
frontend_settings['base_url'] = (
|
||||
get_setting('INVENTREE_FRONTEND_URL_BASE', 'frontend_url_base', 'web')
|
||||
or 'web'
|
||||
)
|
||||
frontend_settings['base_url'] = base_url
|
||||
|
||||
# If provided, specify the API host
|
||||
api_host = frontend_settings.get('api_host', None) or get_setting(
|
||||
|
|
|
|||
|
|
@ -24,8 +24,9 @@ class Command(BaseCommand):
|
|||
def handle(self, *args, **kwargs):
|
||||
"""Export settings information to a JSON file."""
|
||||
from common.models import InvenTreeSetting, InvenTreeUserSetting
|
||||
from InvenTree.config import CONFIG_LOOKUPS
|
||||
|
||||
settings = {'global': {}, 'user': {}}
|
||||
settings = {'global': {}, 'user': {}, 'config': {}}
|
||||
|
||||
# Global settings
|
||||
for key, setting in InvenTreeSetting.SETTINGS.items():
|
||||
|
|
@ -45,6 +46,18 @@ class Command(BaseCommand):
|
|||
'units': str(setting.get('units', '')),
|
||||
}
|
||||
|
||||
# Configuration settings
|
||||
for key, config in CONFIG_LOOKUPS.items():
|
||||
default_value = config.get('default_value', None)
|
||||
if default_value is not None:
|
||||
default_value = str(default_value)
|
||||
|
||||
settings['config'][key] = {
|
||||
'env_var': str(config.get('env_var', '')),
|
||||
'config_key': str(config.get('config_key', '')),
|
||||
'default_value': default_value,
|
||||
}
|
||||
|
||||
filename = kwargs.get('filename', 'inventree_settings.json')
|
||||
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
|
|
|
|||
|
|
@ -345,4 +345,8 @@ def sanitize_svg(
|
|||
link_rel=None,
|
||||
)
|
||||
|
||||
# Replace non-breaking spaces with regular spaces to prevent SVG rendering issues
|
||||
for nbsp in [' ', ' ']:
|
||||
cleaned = cleaned.replace(nbsp, ' ')
|
||||
|
||||
return cleaned
|
||||
|
|
|
|||
|
|
@ -1,12 +1,85 @@
|
|||
"""Configuration settings specific to a particular database backend."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import structlog
|
||||
|
||||
from InvenTree.config import get_boolean_setting, get_setting
|
||||
from InvenTree.config import get_boolean_setting, get_config_value, get_setting
|
||||
|
||||
logger = structlog.get_logger('inventree')
|
||||
|
||||
|
||||
def get_db_backend():
|
||||
"""Return the database backend configuration."""
|
||||
# For the core database configuration values, we test for UPPERCASE configuration values as a backup,
|
||||
# due to legacy reasons (original config files were uppercase,
|
||||
# but we moved to lowercase for consistency with other settings.
|
||||
|
||||
db_config = {
|
||||
'ENGINE': get_setting(
|
||||
'INVENTREE_DB_ENGINE', 'database.engine', '', typecast=str
|
||||
)
|
||||
or get_config_value('database.ENGINE'),
|
||||
'NAME': get_setting('INVENTREE_DB_NAME', 'database.name', '', typecast=str)
|
||||
or get_config_value('database.NAME'),
|
||||
'USER': get_setting('INVENTREE_DB_USER', 'database.user', '', typecast=str)
|
||||
or get_config_value('database.USER'),
|
||||
'PASSWORD': get_setting(
|
||||
'INVENTREE_DB_PASSWORD', 'database.password', '', typecast=str
|
||||
)
|
||||
or get_config_value('database.PASSWORD'),
|
||||
'HOST': get_setting('INVENTREE_DB_HOST', 'database.host', '', typecast=str)
|
||||
or get_config_value('database.HOST'),
|
||||
'PORT': get_setting('INVENTREE_DB_PORT', 'database.port', '', typecast=str)
|
||||
or get_config_value('database.PORT'),
|
||||
'OPTIONS': get_setting(
|
||||
'INVENTREE_DB_OPTIONS', 'database.options', {}, typecast=dict
|
||||
)
|
||||
or get_config_value('database.OPTIONS')
|
||||
or {},
|
||||
}
|
||||
|
||||
# Check for required keys
|
||||
required_keys = ['ENGINE', 'NAME']
|
||||
|
||||
for key in required_keys:
|
||||
if not db_config[key]:
|
||||
raise ValueError(
|
||||
f'Missing required database configuration key: INVENTREE_DB_{key}'
|
||||
)
|
||||
|
||||
DB_ENGINE = db_config['ENGINE'].lower()
|
||||
|
||||
# Correct common misspelling
|
||||
if DB_ENGINE == 'sqlite':
|
||||
DB_ENGINE = 'sqlite3' # pragma: no cover
|
||||
|
||||
if DB_ENGINE in ['sqlite3', 'postgresql', 'mysql']:
|
||||
# Prepend the required python module string
|
||||
DB_ENGINE = f'django.db.backends.{DB_ENGINE}'
|
||||
db_config['ENGINE'] = DB_ENGINE
|
||||
|
||||
if 'sqlite' in DB_ENGINE:
|
||||
db_name = str(Path(db_config['NAME']).resolve())
|
||||
db_config['NAME'] = db_name
|
||||
|
||||
logger.info('DB_ENGINE: %s', DB_ENGINE)
|
||||
logger.info('DB_NAME: %s', db_config['NAME'])
|
||||
logger.info('DB_HOST: %s', db_config.get('HOST', "''"))
|
||||
|
||||
# Set testing options for the database
|
||||
db_config['TEST'] = {'CHARSET': 'utf8'}
|
||||
|
||||
# Set collation option for mysql test database
|
||||
if 'mysql' in DB_ENGINE:
|
||||
db_config['TEST']['COLLATION'] = 'utf8_general_ci' # pragma: no cover
|
||||
|
||||
# Specify database specific configuration
|
||||
set_db_options(DB_ENGINE, db_config['OPTIONS'])
|
||||
|
||||
return db_config
|
||||
|
||||
|
||||
def set_db_options(engine: str, db_options: dict):
|
||||
"""Update database options based on the specified database backend.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
"""Configuration of LDAP support for InvenTree."""
|
||||
|
||||
from InvenTree.config import get_boolean_setting, get_setting
|
||||
|
||||
|
||||
def get_ldap_config(debug: bool = False) -> dict:
|
||||
"""Return a dictionary of LDAP configuration settings.
|
||||
|
||||
The returned settings will be updated into the globals() object,
|
||||
and will be used to configure the LDAP authentication backend.
|
||||
"""
|
||||
import django_auth_ldap.config # type: ignore[unresolved-import]
|
||||
import ldap # type: ignore[unresolved-import]
|
||||
|
||||
# get global options from dict and use ldap.OPT_* as keys and values
|
||||
global_options_dict = get_setting(
|
||||
'INVENTREE_LDAP_GLOBAL_OPTIONS',
|
||||
'ldap.global_options',
|
||||
default_value=None,
|
||||
typecast=dict,
|
||||
)
|
||||
|
||||
global_options = {}
|
||||
|
||||
for k, v in global_options_dict.items():
|
||||
# keys are always ldap.OPT_* constants
|
||||
k_attr = getattr(ldap, k, None)
|
||||
if not k.startswith('OPT_') or k_attr is None:
|
||||
print(f"[LDAP] ldap.global_options, key '{k}' not found, skipping...")
|
||||
continue
|
||||
|
||||
# values can also be other strings, e.g. paths
|
||||
v_attr = v
|
||||
if v.startswith('OPT_'):
|
||||
v_attr = getattr(ldap, v, None)
|
||||
|
||||
if v_attr is None:
|
||||
print(f"[LDAP] ldap.global_options, value key '{v}' not found, skipping...")
|
||||
continue
|
||||
|
||||
global_options[k_attr] = v_attr
|
||||
|
||||
if debug:
|
||||
print('[LDAP] ldap.global_options =', global_options)
|
||||
|
||||
group_type_class = get_setting(
|
||||
'INVENTREE_LDAP_GROUP_TYPE_CLASS',
|
||||
'ldap.group_type_class',
|
||||
'GroupOfUniqueNamesType',
|
||||
str,
|
||||
)
|
||||
|
||||
group_type_class_args = get_setting(
|
||||
'INVENTREE_LDAP_GROUP_TYPE_CLASS_ARGS', 'ldap.group_type_class_args', [], list
|
||||
)
|
||||
|
||||
group_type_class_kwargs = get_setting(
|
||||
'INVENTREE_LDAP_GROUP_TYPE_CLASS_KWARGS',
|
||||
'ldap.group_type_class_kwargs',
|
||||
{'name_attr': 'cn'},
|
||||
dict,
|
||||
)
|
||||
|
||||
group_object_class = get_setting(
|
||||
'INVENTREE_LDAP_GROUP_OBJECT_CLASS',
|
||||
'ldap.group_object_class',
|
||||
'groupOfUniqueNames',
|
||||
str,
|
||||
)
|
||||
|
||||
ldap_config = {
|
||||
'AUTH_LDAP_GLOBAL_OPTIONS': global_options,
|
||||
'AUTH_LDAP_SERVER_URI': get_setting(
|
||||
'INVENTREE_LDAP_SERVER_URI', 'ldap.server_uri'
|
||||
),
|
||||
'AUTH_LDAP_START_TLS': get_boolean_setting(
|
||||
'INVENTREE_LDAP_START_TLS', 'ldap.start_tls', False
|
||||
),
|
||||
'AUTH_LDAP_BIND_DN': get_setting('INVENTREE_LDAP_BIND_DN', 'ldap.bind_dn'),
|
||||
'AUTH_LDAP_BIND_PASSWORD': get_setting(
|
||||
'INVENTREE_LDAP_BIND_PASSWORD', 'ldap.bind_password'
|
||||
),
|
||||
'AUTH_LDAP_USER_SEARCH': django_auth_ldap.config.LDAPSearch(
|
||||
get_setting('INVENTREE_LDAP_SEARCH_BASE_DN', 'ldap.search_base_dn'),
|
||||
ldap.SCOPE_SUBTREE,
|
||||
str(
|
||||
get_setting(
|
||||
'INVENTREE_LDAP_SEARCH_FILTER_STR',
|
||||
'ldap.search_filter_str',
|
||||
'(uid= %(user)s)',
|
||||
)
|
||||
),
|
||||
),
|
||||
'AUTH_LDAP_USER_DN_TEMPLATE': get_setting(
|
||||
'INVENTREE_LDAP_USER_DN_TEMPLATE', 'ldap.user_dn_template'
|
||||
),
|
||||
'AUTH_LDAP_USER_ATTR_MAP': get_setting(
|
||||
'INVENTREE_LDAP_USER_ATTR_MAP',
|
||||
'ldap.user_attr_map',
|
||||
{'first_name': 'givenName', 'last_name': 'sn', 'email': 'mail'},
|
||||
dict,
|
||||
),
|
||||
'AUTH_LDAP_ALWAYS_UPDATE_USER': get_boolean_setting(
|
||||
'INVENTREE_LDAP_ALWAYS_UPDATE_USER', 'ldap.always_update_user', True
|
||||
),
|
||||
'AUTH_LDAP_CACHE_TIMEOUT': get_setting(
|
||||
'INVENTREE_LDAP_CACHE_TIMEOUT', 'ldap.cache_timeout', 3600, int
|
||||
),
|
||||
'AUTH_LDAP_MIRROR_GROUPS': get_boolean_setting(
|
||||
'INVENTREE_LDAP_MIRROR_GROUPS', 'ldap.mirror_groups', False
|
||||
),
|
||||
'AUTH_LDAP_GROUP_OBJECT_CLASS': group_object_class,
|
||||
'AUTH_LDAP_GROUP_SEARCH': django_auth_ldap.config.LDAPSearch(
|
||||
get_setting('INVENTREE_LDAP_GROUP_SEARCH', 'ldap.group_search'),
|
||||
ldap.SCOPE_SUBTREE,
|
||||
f'(objectClass={group_object_class})',
|
||||
),
|
||||
'AUTH_LDAP_GROUP_TYPE_CLASS': group_type_class,
|
||||
'AUTH_LDAP_GROUP_TYPE_CLASS_ARGS': [*group_type_class_args],
|
||||
'AUTH_LDAP_GROUP_TYPE_CLASS_KWARGS': {**group_type_class_kwargs},
|
||||
'AUTH_LDAP_GROUP_TYPE': getattr(django_auth_ldap.config, group_type_class)(
|
||||
*group_type_class_args, **group_type_class_kwargs
|
||||
),
|
||||
'AUTH_LDAP_REQUIRE_GROUP': get_setting(
|
||||
'INVENTREE_LDAP_REQUIRE_GROUP', 'ldap.require_group'
|
||||
),
|
||||
'AUTH_LDAP_DENY_GROUP': get_setting(
|
||||
'INVENTREE_LDAP_DENY_GROUP', 'ldap.deny_group'
|
||||
),
|
||||
'AUTH_LDAP_USER_FLAGS_BY_GROUP': get_setting(
|
||||
'INVENTREE_LDAP_USER_FLAGS_BY_GROUP',
|
||||
'ldap.user_flags_by_group',
|
||||
default_value=None,
|
||||
typecast=dict,
|
||||
),
|
||||
'AUTH_LDAP_FIND_GROUP_PERMS': True,
|
||||
}
|
||||
|
||||
return ldap_config
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
"""Start-up configuration options for InvenTree tracing integrations."""
|
||||
|
||||
import structlog
|
||||
|
||||
from InvenTree.config import get_boolean_setting, get_setting
|
||||
from InvenTree.tracing import setup_instruments, setup_tracing
|
||||
|
||||
logger = structlog.get_logger('inventree')
|
||||
|
||||
|
||||
def configure_tracing(db_engine: str, enabled: bool, tags: dict) -> dict:
|
||||
"""Configure tracing integrations for InvenTree.
|
||||
|
||||
Arguments:
|
||||
db_engine: The database engine being used
|
||||
enabled: Whether tracing is enabled
|
||||
tags: A dictionary of tags to be included in tracing spans, with keys prefixed by
|
||||
"""
|
||||
endpoint = get_setting('INVENTREE_TRACING_ENDPOINT', 'tracing.endpoint', None)
|
||||
headers = (
|
||||
get_setting('INVENTREE_TRACING_HEADERS', 'tracing.headers', None, typecast=dict)
|
||||
or {}
|
||||
)
|
||||
|
||||
if enabled and endpoint:
|
||||
logger.info('Tracing enabled with endpoint: %s', endpoint)
|
||||
|
||||
TRACING_DETAILS = {
|
||||
'endpoint': endpoint,
|
||||
'headers': headers,
|
||||
'resources_input': {
|
||||
**{'inventree.env.' + k: v for k, v in tags.items()},
|
||||
**get_setting(
|
||||
'INVENTREE_TRACING_RESOURCES',
|
||||
'tracing.resources',
|
||||
default_value=None,
|
||||
typecast=dict,
|
||||
),
|
||||
},
|
||||
'console': get_boolean_setting(
|
||||
'INVENTREE_TRACING_CONSOLE', 'tracing.console', False
|
||||
),
|
||||
'auth': get_setting(
|
||||
'INVENTREE_TRACING_AUTH', 'tracing.auth', default_value=None, typecast=dict
|
||||
),
|
||||
'is_http': get_setting('INVENTREE_TRACING_IS_HTTP', 'tracing.is_http', True),
|
||||
'append_http': get_boolean_setting(
|
||||
'INVENTREE_TRACING_APPEND_HTTP', 'tracing.append_http', True
|
||||
),
|
||||
}
|
||||
|
||||
if not enabled:
|
||||
return None
|
||||
|
||||
if endpoint:
|
||||
setup_tracing(**TRACING_DETAILS)
|
||||
setup_instruments(db_engine)
|
||||
else:
|
||||
logger.warning('OpenTelemetry tracing not enabled because endpoint is not set')
|
||||
|
||||
return TRACING_DETAILS
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
"""Configuration settings for the InvenTree background worker process."""
|
||||
|
||||
import sys
|
||||
|
||||
from InvenTree.config import get_setting
|
||||
|
||||
|
||||
def get_worker_config(
|
||||
db_engine: str,
|
||||
global_cache: bool = False,
|
||||
sentry_dsn: str = '',
|
||||
debug: bool = False,
|
||||
) -> dict:
|
||||
"""Return a dictionary of configuration settings for the background worker.
|
||||
|
||||
Arguments:
|
||||
db_engine: The database engine being used (e.g. 'sqlite', 'postgresql', 'mysql')
|
||||
global_cache: Whether a global redis cache is enabled
|
||||
sentry_dsn: The DSN for sentry.io integration (if enabled)
|
||||
debug: Whether the application is running in debug mode
|
||||
|
||||
Ref: https://django-q2.readthedocs.io/en/master/configure.html
|
||||
"""
|
||||
BACKGROUND_WORKER_TIMEOUT = int(
|
||||
get_setting('INVENTREE_BACKGROUND_TIMEOUT', 'background.timeout', 90)
|
||||
)
|
||||
|
||||
# Set the retry time for background workers to be slightly longer than the worker timeout, to ensure that workers have time to timeout before being retried
|
||||
BACKGROUND_WORKER_RETRY = max(
|
||||
int(get_setting('INVENTREE_BACKGROUND_RETRY', 'background.retry', 300)),
|
||||
BACKGROUND_WORKER_TIMEOUT + 120,
|
||||
)
|
||||
|
||||
BACKGROUND_WORKER_ATTEMPTS = int(
|
||||
get_setting('INVENTREE_BACKGROUND_MAX_ATTEMPTS', 'background.max_attempts', 5)
|
||||
)
|
||||
|
||||
# Prevent running multiple background workers if global cache is disabled
|
||||
# This is to prevent scheduling conflicts due to the lack of a shared cache
|
||||
BACKGROUND_WORKER_COUNT = int(
|
||||
get_setting('INVENTREE_BACKGROUND_WORKERS', 'background.workers', 4)
|
||||
)
|
||||
|
||||
# If global cache is disabled, we cannot run multiple background workers
|
||||
if not global_cache:
|
||||
BACKGROUND_WORKER_COUNT = 1
|
||||
|
||||
# If running with SQLite, limit background worker threads to 1 to prevent database locking issues
|
||||
if 'sqlite' in db_engine:
|
||||
BACKGROUND_WORKER_COUNT = 1
|
||||
|
||||
# Check if '--sync' was passed in the command line
|
||||
if '--sync' in sys.argv and '--noreload' in sys.argv and debug:
|
||||
SYNC_TASKS = True
|
||||
else:
|
||||
SYNC_TASKS = False
|
||||
|
||||
# Clean up sys.argv so Django doesn't complain about an unknown argument
|
||||
if SYNC_TASKS:
|
||||
sys.argv.remove('--sync')
|
||||
|
||||
# django-q background worker configuration
|
||||
config = {
|
||||
'name': 'InvenTree',
|
||||
'label': 'Background Tasks',
|
||||
'workers': BACKGROUND_WORKER_COUNT,
|
||||
'timeout': BACKGROUND_WORKER_TIMEOUT,
|
||||
'retry': BACKGROUND_WORKER_RETRY,
|
||||
'max_attempts': BACKGROUND_WORKER_ATTEMPTS,
|
||||
'save_limit': 1000,
|
||||
'queue_limit': 50,
|
||||
'catch_up': False,
|
||||
'bulk': 10,
|
||||
'orm': 'default',
|
||||
'cache': 'default',
|
||||
'sync': SYNC_TASKS,
|
||||
'poll': 1.5,
|
||||
}
|
||||
|
||||
if global_cache:
|
||||
# If using external redis cache, make the cache the broker for Django Q
|
||||
config['django_redis'] = 'worker'
|
||||
|
||||
if sentry_dsn:
|
||||
# If sentry is enabled, configure django-q to report errors to sentry
|
||||
config['error_reporter'] = {'sentry': {'dsn': sentry_dsn}}
|
||||
|
||||
return config
|
||||
|
|
@ -12,7 +12,6 @@ database setup in this file.
|
|||
import logging
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||
|
||||
|
|
@ -33,7 +32,16 @@ from InvenTree.version import checkMinPythonVersion, inventreeCommitHash
|
|||
from users.oauth2_scopes import oauth2_scopes
|
||||
|
||||
from . import config
|
||||
from .setting import db_backend, locales, markdown, spectacular, storages
|
||||
from .setting import (
|
||||
db_backend,
|
||||
ldap,
|
||||
locales,
|
||||
markdown,
|
||||
spectacular,
|
||||
storages,
|
||||
tracing,
|
||||
worker,
|
||||
)
|
||||
|
||||
try:
|
||||
import django_stubs_ext
|
||||
|
|
@ -81,6 +89,8 @@ DEBUG = get_boolean_setting('INVENTREE_DEBUG', 'debug', False)
|
|||
# Internal flag to determine if we are running in docker mode
|
||||
DOCKER = get_boolean_setting('INVENTREE_DOCKER', default_value=False)
|
||||
|
||||
AUTO_UPDATE = get_boolean_setting('INVENTREE_AUTO_UPDATE', 'auto_update', False)
|
||||
|
||||
# Configure logging settings
|
||||
LOG_LEVEL = get_setting('INVENTREE_LOG_LEVEL', 'log_level', 'WARNING')
|
||||
JSON_LOG = get_boolean_setting('INVENTREE_JSON_LOG', 'json_log', False)
|
||||
|
|
@ -222,9 +232,7 @@ PLUGIN_TESTING_EVENTS_ASYNC = False # Flag if events are tested asynchronously
|
|||
PLUGIN_TESTING_RELOAD = False # Flag if plugin reloading is in testing (check_reload)
|
||||
|
||||
# Plugin development settings
|
||||
PLUGIN_DEV_SLUG = (
|
||||
get_setting('INVENTREE_PLUGIN_DEV_SLUG', 'plugin_dev.slug') if DEBUG else None
|
||||
)
|
||||
PLUGIN_DEV_SLUG = get_setting('INVENTREE_PLUGIN_DEV_SLUG', 'plugin_dev.slug')
|
||||
|
||||
PLUGIN_DEV_HOST = get_setting(
|
||||
'INVENTREE_PLUGIN_DEV_HOST', 'plugin_dev.host', 'http://localhost:5174'
|
||||
|
|
@ -386,8 +394,11 @@ MIDDLEWARE = CONFIG.get(
|
|||
|
||||
# In DEBUG mode, add support for django-silk
|
||||
# Ref: https://silk.readthedocs.io/en/latest/
|
||||
DJANGO_SILK_ENABLED = DEBUG and get_boolean_setting( # pragma: no cover
|
||||
'INVENTREE_DEBUG_SILK', 'debug_silk', False
|
||||
DJANGO_SILK_ENABLED = (
|
||||
get_boolean_setting( # pragma: no cover
|
||||
'INVENTREE_DEBUG_SILK', 'debug_silk', False
|
||||
)
|
||||
and DEBUG
|
||||
)
|
||||
|
||||
if DJANGO_SILK_ENABLED: # pragma: no cover
|
||||
|
|
@ -401,8 +412,11 @@ if DJANGO_SILK_ENABLED: # pragma: no cover
|
|||
|
||||
# In DEBUG mode, add support for django-querycount
|
||||
# Ref: https://github.com/bradmontgomery/django-querycount
|
||||
if DEBUG and get_boolean_setting( # pragma: no cover
|
||||
'INVENTREE_DEBUG_QUERYCOUNT', 'debug_querycount', False
|
||||
if (
|
||||
get_boolean_setting( # pragma: no cover
|
||||
'INVENTREE_DEBUG_QUERYCOUNT', 'debug_querycount', False
|
||||
)
|
||||
and DEBUG
|
||||
):
|
||||
MIDDLEWARE.append('querycount.middleware.QueryCountMiddleware')
|
||||
logger.debug('Running with debug_querycount middleware enabled')
|
||||
|
|
@ -437,14 +451,14 @@ AUTHENTICATION_BACKENDS = (
|
|||
|
||||
# LDAP support
|
||||
LDAP_AUTH = get_boolean_setting('INVENTREE_LDAP_ENABLED', 'ldap.enabled', False)
|
||||
if LDAP_AUTH: # pragma: no cover
|
||||
import django_auth_ldap.config # type: ignore[unresolved-import]
|
||||
import ldap # type: ignore[unresolved-import]
|
||||
LDAP_DEBUG = (
|
||||
get_boolean_setting('INVENTREE_LDAP_DEBUG', 'ldap.debug', False) and LDAP_AUTH
|
||||
)
|
||||
|
||||
if LDAP_AUTH: # pragma: no cover
|
||||
AUTHENTICATION_BACKENDS.append('django_auth_ldap.backend.LDAPBackend')
|
||||
|
||||
# debug mode to troubleshoot configuration
|
||||
LDAP_DEBUG = get_boolean_setting('INVENTREE_LDAP_DEBUG', 'ldap.debug', False)
|
||||
if LDAP_DEBUG:
|
||||
if 'loggers' not in LOGGING:
|
||||
LOGGING['loggers'] = {}
|
||||
|
|
@ -453,113 +467,10 @@ if LDAP_AUTH: # pragma: no cover
|
|||
'handlers': DEFAULT_LOG_HANDLER,
|
||||
}
|
||||
|
||||
# get global options from dict and use ldap.OPT_* as keys and values
|
||||
global_options_dict = get_setting(
|
||||
'INVENTREE_LDAP_GLOBAL_OPTIONS',
|
||||
'ldap.global_options',
|
||||
default_value=None,
|
||||
typecast=dict,
|
||||
)
|
||||
global_options = {}
|
||||
for k, v in global_options_dict.items():
|
||||
# keys are always ldap.OPT_* constants
|
||||
k_attr = getattr(ldap, k, None)
|
||||
if not k.startswith('OPT_') or k_attr is None:
|
||||
print(f"[LDAP] ldap.global_options, key '{k}' not found, skipping...")
|
||||
continue
|
||||
# Determine LDAP settings
|
||||
ldap_settings = ldap.get_ldap_config(debug=LDAP_DEBUG)
|
||||
globals().update(ldap_settings)
|
||||
|
||||
# values can also be other strings, e.g. paths
|
||||
v_attr = v
|
||||
if v.startswith('OPT_'):
|
||||
v_attr = getattr(ldap, v, None)
|
||||
|
||||
if v_attr is None:
|
||||
print(f"[LDAP] ldap.global_options, value key '{v}' not found, skipping...")
|
||||
continue
|
||||
|
||||
global_options[k_attr] = v_attr
|
||||
AUTH_LDAP_GLOBAL_OPTIONS = global_options
|
||||
if LDAP_DEBUG:
|
||||
print('[LDAP] ldap.global_options =', global_options)
|
||||
|
||||
AUTH_LDAP_SERVER_URI = get_setting('INVENTREE_LDAP_SERVER_URI', 'ldap.server_uri')
|
||||
AUTH_LDAP_START_TLS = get_boolean_setting(
|
||||
'INVENTREE_LDAP_START_TLS', 'ldap.start_tls', False
|
||||
)
|
||||
AUTH_LDAP_BIND_DN = get_setting('INVENTREE_LDAP_BIND_DN', 'ldap.bind_dn')
|
||||
AUTH_LDAP_BIND_PASSWORD = get_setting(
|
||||
'INVENTREE_LDAP_BIND_PASSWORD', 'ldap.bind_password'
|
||||
)
|
||||
AUTH_LDAP_USER_SEARCH = django_auth_ldap.config.LDAPSearch(
|
||||
get_setting('INVENTREE_LDAP_SEARCH_BASE_DN', 'ldap.search_base_dn'),
|
||||
ldap.SCOPE_SUBTREE,
|
||||
str(
|
||||
get_setting(
|
||||
'INVENTREE_LDAP_SEARCH_FILTER_STR',
|
||||
'ldap.search_filter_str',
|
||||
'(uid= %(user)s)',
|
||||
)
|
||||
),
|
||||
)
|
||||
AUTH_LDAP_USER_DN_TEMPLATE = get_setting(
|
||||
'INVENTREE_LDAP_USER_DN_TEMPLATE', 'ldap.user_dn_template'
|
||||
)
|
||||
AUTH_LDAP_USER_ATTR_MAP = get_setting(
|
||||
'INVENTREE_LDAP_USER_ATTR_MAP',
|
||||
'ldap.user_attr_map',
|
||||
{'first_name': 'givenName', 'last_name': 'sn', 'email': 'mail'},
|
||||
dict,
|
||||
)
|
||||
AUTH_LDAP_ALWAYS_UPDATE_USER = get_boolean_setting(
|
||||
'INVENTREE_LDAP_ALWAYS_UPDATE_USER', 'ldap.always_update_user', True
|
||||
)
|
||||
AUTH_LDAP_CACHE_TIMEOUT = get_setting(
|
||||
'INVENTREE_LDAP_CACHE_TIMEOUT', 'ldap.cache_timeout', 3600, int
|
||||
)
|
||||
|
||||
AUTH_LDAP_MIRROR_GROUPS = get_boolean_setting(
|
||||
'INVENTREE_LDAP_MIRROR_GROUPS', 'ldap.mirror_groups', False
|
||||
)
|
||||
AUTH_LDAP_GROUP_OBJECT_CLASS = get_setting(
|
||||
'INVENTREE_LDAP_GROUP_OBJECT_CLASS',
|
||||
'ldap.group_object_class',
|
||||
'groupOfUniqueNames',
|
||||
str,
|
||||
)
|
||||
AUTH_LDAP_GROUP_SEARCH = django_auth_ldap.config.LDAPSearch(
|
||||
get_setting('INVENTREE_LDAP_GROUP_SEARCH', 'ldap.group_search'),
|
||||
ldap.SCOPE_SUBTREE,
|
||||
f'(objectClass={AUTH_LDAP_GROUP_OBJECT_CLASS})',
|
||||
)
|
||||
AUTH_LDAP_GROUP_TYPE_CLASS = get_setting(
|
||||
'INVENTREE_LDAP_GROUP_TYPE_CLASS',
|
||||
'ldap.group_type_class',
|
||||
'GroupOfUniqueNamesType',
|
||||
str,
|
||||
)
|
||||
AUTH_LDAP_GROUP_TYPE_CLASS_ARGS = get_setting(
|
||||
'INVENTREE_LDAP_GROUP_TYPE_CLASS_ARGS', 'ldap.group_type_class_args', [], list
|
||||
)
|
||||
AUTH_LDAP_GROUP_TYPE_CLASS_KWARGS = get_setting(
|
||||
'INVENTREE_LDAP_GROUP_TYPE_CLASS_KWARGS',
|
||||
'ldap.group_type_class_kwargs',
|
||||
{'name_attr': 'cn'},
|
||||
dict,
|
||||
)
|
||||
AUTH_LDAP_GROUP_TYPE = getattr(django_auth_ldap.config, AUTH_LDAP_GROUP_TYPE_CLASS)(
|
||||
*AUTH_LDAP_GROUP_TYPE_CLASS_ARGS, **AUTH_LDAP_GROUP_TYPE_CLASS_KWARGS
|
||||
)
|
||||
AUTH_LDAP_REQUIRE_GROUP = get_setting(
|
||||
'INVENTREE_LDAP_REQUIRE_GROUP', 'ldap.require_group'
|
||||
)
|
||||
AUTH_LDAP_DENY_GROUP = get_setting('INVENTREE_LDAP_DENY_GROUP', 'ldap.deny_group')
|
||||
AUTH_LDAP_USER_FLAGS_BY_GROUP = get_setting(
|
||||
'INVENTREE_LDAP_USER_FLAGS_BY_GROUP',
|
||||
'ldap.user_flags_by_group',
|
||||
default_value=None,
|
||||
typecast=dict,
|
||||
)
|
||||
AUTH_LDAP_FIND_GROUP_PERMS = True
|
||||
|
||||
# Allow secure http developer server in debug mode
|
||||
if DEBUG:
|
||||
|
|
@ -642,107 +553,11 @@ Configure the database backend based on the user-specified values.
|
|||
|
||||
logger.debug('Configuring database backend:')
|
||||
|
||||
# Extract database configuration from the config.yaml file
|
||||
db_config = CONFIG.get('database', None) if CONFIG else None
|
||||
# Load database configuration from the config file and environment variables
|
||||
database = db_backend.get_db_backend()
|
||||
DB_ENGINE = database['ENGINE']
|
||||
|
||||
if not db_config:
|
||||
db_config = {}
|
||||
|
||||
# Environment variables take preference over config file!
|
||||
|
||||
db_keys = ['ENGINE', 'NAME', 'USER', 'PASSWORD', 'HOST', 'PORT']
|
||||
|
||||
for key in db_keys:
|
||||
# First, check the environment variables
|
||||
env_key = f'INVENTREE_DB_{key}'
|
||||
env_var = os.environ.get(env_key, None)
|
||||
|
||||
if env_var:
|
||||
# Make use PORT is int
|
||||
if key == 'PORT':
|
||||
try:
|
||||
env_var = int(env_var)
|
||||
except ValueError:
|
||||
logger.exception('Invalid number for %s: %s', env_key, env_var)
|
||||
# Override configuration value
|
||||
db_config[key] = env_var
|
||||
|
||||
# Check that required database configuration options are specified
|
||||
required_keys = ['ENGINE', 'NAME']
|
||||
|
||||
# Ensure all database keys are upper case
|
||||
db_config = {key.upper(): value for key, value in db_config.items()}
|
||||
|
||||
for key in required_keys:
|
||||
if key not in db_config: # pragma: no cover
|
||||
error_msg = (
|
||||
f'Missing required database configuration value `INVENTREE_DB_{key}`'
|
||||
)
|
||||
logger.error(error_msg)
|
||||
|
||||
print('Error: ' + error_msg)
|
||||
sys.exit(-1)
|
||||
|
||||
"""
|
||||
Special considerations for the database 'ENGINE' setting.
|
||||
It can be specified in config.yaml (or envvar) as either (for example):
|
||||
- sqlite3
|
||||
- django.db.backends.sqlite3
|
||||
- django.db.backends.postgresql
|
||||
"""
|
||||
|
||||
DB_ENGINE = db_config['ENGINE'].lower()
|
||||
|
||||
# Correct common misspelling
|
||||
if DB_ENGINE == 'sqlite':
|
||||
DB_ENGINE = 'sqlite3' # pragma: no cover
|
||||
|
||||
if DB_ENGINE in ['sqlite3', 'postgresql', 'mysql']:
|
||||
# Prepend the required python module string
|
||||
DB_ENGINE = f'django.db.backends.{DB_ENGINE}'
|
||||
db_config['ENGINE'] = DB_ENGINE
|
||||
|
||||
db_name = db_config['NAME']
|
||||
db_host = db_config.get('HOST', "''")
|
||||
|
||||
if 'sqlite' in DB_ENGINE:
|
||||
db_name = str(Path(db_name).resolve())
|
||||
db_config['NAME'] = db_name
|
||||
|
||||
logger.info('DB_ENGINE: %s', DB_ENGINE)
|
||||
logger.info('DB_NAME: %s', db_name)
|
||||
logger.info('DB_HOST: %s', db_host)
|
||||
|
||||
"""
|
||||
In addition to base-level database configuration, we may wish to specify specific options to the database backend
|
||||
Ref: https://docs.djangoproject.com/en/3.2/ref/settings/#std:setting-OPTIONS
|
||||
"""
|
||||
|
||||
# 'OPTIONS' or 'options' can be specified in config.yaml
|
||||
# Set useful sensible timeouts for a transactional webserver to communicate
|
||||
# with its database server, that is, if the webserver is having issues
|
||||
# connecting to the database server (such as a replica failover) don't sit and
|
||||
# wait for possibly an hour or more, just tell the client something went wrong
|
||||
# and let the client retry when they want to.
|
||||
db_options = db_config.get('OPTIONS', db_config.get('options'))
|
||||
|
||||
if db_options is None:
|
||||
db_options = {}
|
||||
|
||||
# Set database-specific options
|
||||
db_backend.set_db_options(DB_ENGINE, db_options)
|
||||
|
||||
# Provide OPTIONS dict back to the database configuration dict
|
||||
db_config['OPTIONS'] = db_options
|
||||
|
||||
# Set testing options for the database
|
||||
db_config['TEST'] = {'CHARSET': 'utf8'}
|
||||
|
||||
# Set collation option for mysql test database
|
||||
if 'mysql' in DB_ENGINE:
|
||||
db_config['TEST']['COLLATION'] = 'utf8_general_ci' # pragma: no cover
|
||||
|
||||
DATABASES = {'default': db_config}
|
||||
DATABASES = {'default': database}
|
||||
|
||||
# login settings
|
||||
REMOTE_LOGIN = get_boolean_setting(
|
||||
|
|
@ -776,133 +591,28 @@ if SENTRY_ENABLED and SENTRY_DSN and not TESTING: # pragma: no cover
|
|||
init_sentry(SENTRY_DSN, SENTRY_SAMPLE_RATE, inventree_tags)
|
||||
|
||||
# OpenTelemetry tracing
|
||||
TRACING_ENABLED = get_boolean_setting(
|
||||
'INVENTREE_TRACING_ENABLED', 'tracing.enabled', False
|
||||
TRACING_ENABLED = (
|
||||
get_boolean_setting('INVENTREE_TRACING_ENABLED', 'tracing.enabled', False)
|
||||
and not isRunningBackup()
|
||||
)
|
||||
TRACING_DETAILS: Optional[dict] = None
|
||||
|
||||
if TRACING_ENABLED and not isRunningBackup(): # pragma: no cover
|
||||
from InvenTree.tracing import setup_instruments, setup_tracing
|
||||
|
||||
_t_endpoint = get_setting('INVENTREE_TRACING_ENDPOINT', 'tracing.endpoint', None)
|
||||
_t_headers = get_setting('INVENTREE_TRACING_HEADERS', 'tracing.headers', None, dict)
|
||||
|
||||
if _t_headers is None:
|
||||
_t_headers = {}
|
||||
|
||||
if _t_endpoint:
|
||||
logger.info('OpenTelemetry tracing enabled')
|
||||
|
||||
TRACING_DETAILS = (
|
||||
TRACING_DETAILS
|
||||
if TRACING_DETAILS
|
||||
else {
|
||||
'endpoint': _t_endpoint,
|
||||
'headers': _t_headers,
|
||||
'resources_input': {
|
||||
**{'inventree.env.' + k: v for k, v in inventree_tags.items()},
|
||||
**get_setting(
|
||||
'INVENTREE_TRACING_RESOURCES',
|
||||
'tracing.resources',
|
||||
default_value=None,
|
||||
typecast=dict,
|
||||
),
|
||||
},
|
||||
'console': get_boolean_setting(
|
||||
'INVENTREE_TRACING_CONSOLE', 'tracing.console', False
|
||||
),
|
||||
'auth': get_setting(
|
||||
'INVENTREE_TRACING_AUTH',
|
||||
'tracing.auth',
|
||||
default_value=None,
|
||||
typecast=dict,
|
||||
),
|
||||
'is_http': get_setting(
|
||||
'INVENTREE_TRACING_IS_HTTP', 'tracing.is_http', True
|
||||
),
|
||||
'append_http': get_boolean_setting(
|
||||
'INVENTREE_TRACING_APPEND_HTTP', 'tracing.append_http', True
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
# Run tracing/logging instrumentation
|
||||
setup_tracing(**TRACING_DETAILS)
|
||||
setup_instruments(DB_ENGINE)
|
||||
else:
|
||||
logger.warning('OpenTelemetry tracing not enabled because endpoint is not set')
|
||||
|
||||
# endregion
|
||||
TRACING_DETAILS: Optional[dict] = tracing.configure_tracing(
|
||||
DB_ENGINE, TRACING_ENABLED, inventree_tags
|
||||
)
|
||||
|
||||
# Cache configuration
|
||||
GLOBAL_CACHE_ENABLED = is_global_cache_enabled()
|
||||
|
||||
CACHES = {'default': get_cache_config(GLOBAL_CACHE_ENABLED)}
|
||||
|
||||
BACKGROUND_WORKER_TIMEOUT = int(
|
||||
get_setting('INVENTREE_BACKGROUND_TIMEOUT', 'background.timeout', 90)
|
||||
# Background task processing with django-q
|
||||
Q_CLUSTER = worker.get_worker_config(
|
||||
DB_ENGINE,
|
||||
global_cache=GLOBAL_CACHE_ENABLED,
|
||||
sentry_dsn=SENTRY_DSN if SENTRY_ENABLED and SENTRY_DSN else None,
|
||||
debug=DEBUG,
|
||||
)
|
||||
|
||||
# Set the retry time for background workers to be slightly longer than the worker timeout, to ensure that workers have time to timeout before being retried
|
||||
BACKGROUND_WORKER_RETRY = max(
|
||||
int(get_setting('INVENTREE_BACKGROUND_RETRY', 'background.retry', 300)),
|
||||
BACKGROUND_WORKER_TIMEOUT + 120,
|
||||
)
|
||||
|
||||
# Prevent running multiple background workers if global cache is disabled
|
||||
# This is to prevent scheduling conflicts due to the lack of a shared cache
|
||||
BACKGROUND_WORKER_COUNT = (
|
||||
int(get_setting('INVENTREE_BACKGROUND_WORKERS', 'background.workers', 4))
|
||||
if GLOBAL_CACHE_ENABLED
|
||||
else 1
|
||||
)
|
||||
|
||||
# If running with SQLite, limit background worker threads to 1 to prevent database locking issues
|
||||
if 'sqlite' in DB_ENGINE:
|
||||
BACKGROUND_WORKER_COUNT = 1
|
||||
|
||||
BACKGROUND_WORKER_ATTEMPTS = int(
|
||||
get_setting('INVENTREE_BACKGROUND_MAX_ATTEMPTS', 'background.max_attempts', 5)
|
||||
)
|
||||
|
||||
# Check if '--sync' was passed in the command line
|
||||
if '--sync' in sys.argv and '--noreload' in sys.argv and DEBUG:
|
||||
SYNC_TASKS = True
|
||||
else:
|
||||
SYNC_TASKS = False
|
||||
|
||||
# Clean up sys.argv so Django doesn't complain about an unknown argument
|
||||
if SYNC_TASKS:
|
||||
sys.argv.remove('--sync')
|
||||
|
||||
# django-q background worker configuration
|
||||
Q_CLUSTER = {
|
||||
'name': 'InvenTree',
|
||||
'label': 'Background Tasks',
|
||||
'workers': BACKGROUND_WORKER_COUNT,
|
||||
'timeout': BACKGROUND_WORKER_TIMEOUT,
|
||||
'retry': BACKGROUND_WORKER_RETRY,
|
||||
'max_attempts': BACKGROUND_WORKER_ATTEMPTS,
|
||||
'save_limit': 1000,
|
||||
'queue_limit': 50,
|
||||
'catch_up': False,
|
||||
'bulk': 10,
|
||||
'orm': 'default',
|
||||
'cache': 'default',
|
||||
'sync': SYNC_TASKS,
|
||||
'poll': 1.5,
|
||||
}
|
||||
|
||||
# Configure django-q sentry integration
|
||||
if SENTRY_ENABLED and SENTRY_DSN: # pragma: no cover
|
||||
Q_CLUSTER['error_reporter'] = {'sentry': {'dsn': SENTRY_DSN}}
|
||||
|
||||
if GLOBAL_CACHE_ENABLED: # pragma: no cover
|
||||
# If using external redis cache, make the cache the broker for Django Q
|
||||
# as well
|
||||
Q_CLUSTER['django_redis'] = 'worker'
|
||||
|
||||
|
||||
SILENCED_SYSTEM_CHECKS = ['templates.E003', 'templates.W003']
|
||||
|
||||
# Password validation
|
||||
|
|
@ -976,10 +686,10 @@ INTERNAL_EMAIL_BACKEND = get_setting(
|
|||
)
|
||||
|
||||
# SMTP backend
|
||||
EMAIL_HOST = get_setting('INVENTREE_EMAIL_HOST', 'email.host', '')
|
||||
EMAIL_HOST = get_setting('INVENTREE_EMAIL_HOST', 'email.host')
|
||||
EMAIL_PORT = get_setting('INVENTREE_EMAIL_PORT', 'email.port', 25, typecast=int)
|
||||
EMAIL_HOST_USER = get_setting('INVENTREE_EMAIL_USERNAME', 'email.username', '')
|
||||
EMAIL_HOST_PASSWORD = get_setting('INVENTREE_EMAIL_PASSWORD', 'email.password', '')
|
||||
EMAIL_HOST_USER = get_setting('INVENTREE_EMAIL_USERNAME', 'email.username')
|
||||
EMAIL_HOST_PASSWORD = get_setting('INVENTREE_EMAIL_PASSWORD', 'email.password')
|
||||
EMAIL_USE_TLS = get_boolean_setting('INVENTREE_EMAIL_TLS', 'email.tls', False)
|
||||
EMAIL_USE_SSL = get_boolean_setting('INVENTREE_EMAIL_SSL', 'email.ssl', False)
|
||||
# Anymail
|
||||
|
|
@ -1150,16 +860,14 @@ LANGUAGE_COOKIE_SAMESITE = COOKIE_MODE
|
|||
- Otherwise, use the value specified in the configuration file (or env var)
|
||||
"""
|
||||
COOKIE_SECURE = (
|
||||
False
|
||||
if DEBUG
|
||||
else (
|
||||
SESSION_COOKIE_SAMESITE == 'None'
|
||||
or get_boolean_setting(
|
||||
'INVENTREE_SESSION_COOKIE_SECURE', 'cookie.secure', False
|
||||
)
|
||||
)
|
||||
get_boolean_setting('INVENTREE_SESSION_COOKIE_SECURE', 'cookie.secure', False)
|
||||
or SESSION_COOKIE_SAMESITE == 'None'
|
||||
)
|
||||
|
||||
# Override COOKIE_SECURE value in DEBUG mode
|
||||
if DEBUG:
|
||||
COOKIE_SECURE = False
|
||||
|
||||
CSRF_COOKIE_SECURE = COOKIE_SECURE
|
||||
SESSION_COOKIE_SECURE = COOKIE_SECURE
|
||||
LANGUAGE_COOKIE_SECURE = COOKIE_SECURE
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ from maintenance_mode.core import (
|
|||
from opentelemetry import trace
|
||||
|
||||
from common.settings import get_global_setting, set_global_setting
|
||||
from InvenTree.config import get_setting
|
||||
from plugin import registry
|
||||
|
||||
from .version import isInvenTreeUpToDate
|
||||
|
|
@ -822,7 +821,7 @@ def check_for_migrations(force: bool = False, reload_registry: bool = True) -> b
|
|||
set_pending_migrations(n)
|
||||
|
||||
# Test if auto-updates are enabled
|
||||
if not force and not get_setting('INVENTREE_AUTO_UPDATE', 'auto_update'):
|
||||
if not force and not settings.AUTO_UPDATE:
|
||||
logger.info('Auto-update is disabled - skipping migrations')
|
||||
return False
|
||||
|
||||
|
|
|
|||
|
|
@ -386,6 +386,20 @@ class BuildList(
|
|||
kwargs['create'] = True
|
||||
return super().get_serializer(*args, **kwargs)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
"""Save user information on order creation."""
|
||||
serializer = self.get_serializer(data=self.clean_data(request.data))
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
build = serializer.save()
|
||||
build.issued_by = request.user
|
||||
build.save()
|
||||
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(
|
||||
serializer.data, status=status.HTTP_201_CREATED, headers=headers
|
||||
)
|
||||
|
||||
|
||||
class BuildDetail(BuildMixin, RetrieveUpdateDestroyAPI):
|
||||
"""API endpoint for detail view of a Build object."""
|
||||
|
|
|
|||
|
|
@ -403,6 +403,14 @@ class Build(
|
|||
related_name='builds_issued',
|
||||
)
|
||||
|
||||
@property
|
||||
def created_by(self):
|
||||
"""Alias for issued_by field.
|
||||
|
||||
This is used for compatibility with the order models
|
||||
"""
|
||||
return self.issued_by
|
||||
|
||||
responsible = models.ForeignKey(
|
||||
users.models.Owner,
|
||||
on_delete=models.SET_NULL,
|
||||
|
|
|
|||
|
|
@ -104,7 +104,8 @@ class BuildSerializer(
|
|||
read_only_fields = [
|
||||
'completed',
|
||||
'creation_date',
|
||||
'completion_data',
|
||||
'issued_by',
|
||||
'completion_date',
|
||||
'status',
|
||||
'status_text',
|
||||
'level',
|
||||
|
|
|
|||
|
|
@ -568,6 +568,9 @@ class BuildTest(BuildAPITest):
|
|||
|
||||
self.assertEqual(bo.children.count(), 0)
|
||||
|
||||
self.assertIsNotNone(bo.issued_by)
|
||||
self.assertEqual(bo.issued_by, self.user)
|
||||
|
||||
|
||||
class BuildAllocationTest(BuildAPITest):
|
||||
"""Unit tests for allocation of stock items against a build order.
|
||||
|
|
|
|||
|
|
@ -395,21 +395,8 @@ class NotificationMessageViewSet(
|
|||
queryset = queryset.filter(user=request.user)
|
||||
return queryset
|
||||
|
||||
def get_permissions(self):
|
||||
"""Override permissions for list view."""
|
||||
if self.action == 'list':
|
||||
return [IsAuthenticatedOrReadScope()]
|
||||
else:
|
||||
return super().get_permissions()
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
"""List view for all notifications of the current user."""
|
||||
# TODO @matmair permissions for this are currently being overwritten in get_permissions - this should be moved to a dedicated endpoint
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
# TODO @matmair this should really be a POST
|
||||
@action(
|
||||
detail=False, methods=['get'], permission_classes=[IsAuthenticatedOrReadScope]
|
||||
detail=False, methods=['post'], permission_classes=[IsAuthenticatedOrReadScope]
|
||||
)
|
||||
def readall(self, request, *args, **kwargs):
|
||||
"""Set all messages for the current user as read."""
|
||||
|
|
@ -1248,7 +1235,6 @@ class EmailViewSet(BulkDeleteViewsetMixin, RetrieveDestroyModelViewSet):
|
|||
'thread_id_key',
|
||||
]
|
||||
|
||||
@extend_schema(responses={201: common.serializers.TestEmailSerializer})
|
||||
@action(
|
||||
detail=False,
|
||||
methods=['post'],
|
||||
|
|
@ -1270,8 +1256,7 @@ class EmailViewSet(BulkDeleteViewsetMixin, RetrieveDestroyModelViewSet):
|
|||
raise serializers.ValidationError(
|
||||
detail=f'Failed to send test email: "{reason}"'
|
||||
) # pragma: no cover
|
||||
# TODO @matmair - breaking change: this should be a 200
|
||||
return Response(serializer.data, status=201)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
admin_router.register('email', EmailViewSet, basename='api-email')
|
||||
|
|
|
|||
|
|
@ -381,7 +381,16 @@ class ConfigSerializer(serializers.Serializer):
|
|||
"""Return the configuration data as a dictionary."""
|
||||
if not isinstance(instance, str):
|
||||
instance = list(instance.keys())[0]
|
||||
return {'key': instance, **self.instance.get(instance)}
|
||||
|
||||
data = {'key': instance}
|
||||
|
||||
for k, v in self.instance.get(instance, {}).items():
|
||||
if k == 'default_value':
|
||||
# Skip sensitive default values
|
||||
continue
|
||||
data[k] = v
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class NotesImageSerializer(InvenTreeModelSerializer):
|
||||
|
|
|
|||
|
|
@ -251,6 +251,12 @@ USER_SETTINGS: dict[str, InvenTreeSettingsKeyType] = {
|
|||
'default': False,
|
||||
'validator': bool,
|
||||
},
|
||||
'SHOW_BOM_SUBASSEMBLY_LEVELS': {
|
||||
'name': _('Show Subassemblies in BOM table'),
|
||||
'description': _('Enable display of subassemblies in the BOM table'),
|
||||
'default': True,
|
||||
'validator': bool,
|
||||
},
|
||||
'NOTIFICATION_ERROR_REPORT': {
|
||||
'name': _('Receive error reports'),
|
||||
'description': _('Receive notifications for system errors'),
|
||||
|
|
|
|||
|
|
@ -103,7 +103,9 @@ class EmailTests(InvenTreeAPITestCase):
|
|||
)
|
||||
def test_email_api(self):
|
||||
"""Test that the email api endpoints work."""
|
||||
self.post(reverse('api-email-test'), {'email': 'test@example.org'})
|
||||
self.post(
|
||||
reverse('api-email-test'), {'email': 'test@example.org'}, expected_code=200
|
||||
)
|
||||
|
||||
response = self.get(reverse('api-email-list'), expected_code=200)
|
||||
self.assertIn('subject', response.data[0])
|
||||
|
|
|
|||
|
|
@ -1332,7 +1332,7 @@ class NotificationTest(InvenTreeAPITestCase):
|
|||
self.assertEqual(len(self.get(url, expected_code=200).data), 1)
|
||||
|
||||
# Read with readall endpoint
|
||||
self.get(reverse('api-notifications-readall'), {}, expected_code=200)
|
||||
self.post(reverse('api-notifications-readall'), {}, expected_code=200)
|
||||
|
||||
self.assertEqual(NotificationMessage.objects.filter(read=True).count(), 1)
|
||||
self.assertEqual(len(self.get(url, expected_code=200).data), 1)
|
||||
|
|
|
|||
|
|
@ -18,15 +18,15 @@
|
|||
# TO MAINTAINERS: Do not change database strings
|
||||
database:
|
||||
# --- Available options: ---
|
||||
# ENGINE: Database engine. Selection from:
|
||||
# engine: Database engine. Selection from:
|
||||
# - mysql
|
||||
# - postgresql
|
||||
# - sqlite3
|
||||
# NAME: Database name
|
||||
# USER: Database username (if required)
|
||||
# PASSWORD: Database password (if required)
|
||||
# HOST: Database host address (if required)
|
||||
# PORT: Database host port (if required)
|
||||
# name: Database name
|
||||
# user: Database username (if required)
|
||||
# password: Database password (if required)
|
||||
# host: Database host address (if required)
|
||||
# port: Database host port (if required)
|
||||
|
||||
# Base URL for the InvenTree server (or use the environment variable INVENTREE_SITE_URL)
|
||||
# site_url: 'http://localhost:8000'
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ class TransitionMethod:
|
|||
result: bool - True if the transition method was successful, False otherwise.
|
||||
|
||||
Raises:
|
||||
ValidationError: Alert the user that the transition failued
|
||||
ValidationError: Alert the user that the transition failed
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
'TransitionMethod.transition must be implemented'
|
||||
|
|
|
|||
|
|
@ -599,6 +599,7 @@ class PartValidateBOM(RetrieveUpdateAPI):
|
|||
|
||||
queryset = Part.objects.all()
|
||||
serializer_class = part_serializers.PartBomValidateSerializer
|
||||
role_required = 'bom.change'
|
||||
|
||||
@extend_schema(
|
||||
responses={
|
||||
|
|
@ -1056,6 +1057,7 @@ class PartList(
|
|||
filter_backends = SEARCH_ORDER_FILTER
|
||||
|
||||
ordering_fields = [
|
||||
'id',
|
||||
'name',
|
||||
'creation_date',
|
||||
'IPN',
|
||||
|
|
|
|||